天天看点

Hibernate关闭Connection链接

    通常程序员都要通过调用close()方法的代码方式去关闭一个流或者链接。现在让我们来模拟一下不关闭流会

    发生什么。请看以下代码:

        import java.sql.Connection;

        import java.sql.SQLException;

        import dbc.DatabaseConnection;

        public class Text {

            public static void main(String[] args) {

                Connection conn=null;

                DatabaseConnection dbc=new DatabaseConnection();

                int i=0;

                for(;;){

                    conn=dbc.getConnection();

                    System.out.println(conn);

                    System.out.println(++i);

                }

            }

        }

    以上代码并没有调用关闭链接的方法,当程序循环到了16450次左右,JVM抛出了一个异常:

    com.mysql.jdbc.exceptions.jdbc4.CommunicationsException:Communications link failure;

    异常描叙:

    The last packet sent successfully to the server was 0 milliseconds ago. The driver

    has not received any packets from the server.

    最后一个成功发送到服务器的数据包是0毫秒前的。该驱动程序没有从服务器接收到任何数据包。

    此时程序依旧还在运行之中,没有因为异常而终止,但是可以发现,当异常发生之后,再输出conn对象

    的地址的时候,输出的都是异常发生前的那一个对象的地址,这说明dbc.getConnection()没有再产生新的

    链接。

    初步分析这是一个通信链接异常,造成的原因是链接占用的资源没有得到释放,导致内存溢出;

    我们都知道,当一个对象的引用指向其他对象的时候,这个失去句柄的对象会被判定死亡被垃圾回收器自

    动回收,以防止内存泄漏和内存溢出。然后我们可能就会进入一个误区,失去句柄的链接会被自动清理,

    显然自动清理并没有发生,垃圾回收器只会负责释放那些经由new形式创建的对象占据的内存,假定某对象

    通过特殊方式获得了一个块“特殊”的内存,比如说流,或者链接,垃圾回收器则不会自动回收这特殊的内存。

    进而我们又发现了一个有趣的现象,请看以下代码:

        import java.sql.Connection;

        import java.sql.SQLException;

        import java.util.Date;

        import java.beans.PropertyVetoException;

        import com.mchange.v2.c3p0.ComboPooledDataSource;

        public class Text {

            @SuppressWarnings({ "unused", "static-access" })

            public static void main(String[] args) {

                Long start = new Date().getTime();

                Connection conn = null;

                C3p0Util c3 = C3p0Util.getInstance();

                for (int i = 0; i < 100000; i++) {

                    conn = c3.getConnection();

                    System.out.println(i);

                    try {

                        System.out.println("这是链接池中的工作链接数"+c3.comboPooledDataSource.getNumBusyConnections());

                    } catch (SQLException e) {

                        e.printStackTrace();

                    }

                }

                Long end = new Date().getTime();

                System.out.println("耗时: " + (end - start));

            }

        }

        class C3p0Util {

            private static final String DRIVER = "com.mysql.jdbc.Driver";

            private static final String URL = "jdbc:mysql://localhost:3306/hibernate?useSSL=true&useUnicode=true&CharacterEncoding=UTF-8";

            private static final String USER = "root";

            private static final String PASSWORD = "1234";

            private static C3p0Util Instance = null;

            static ComboPooledDataSource comboPooledDataSource = null;

            private C3p0Util() {

                comboPooledDataSource = new ComboPooledDataSource();

                try {

                    comboPooledDataSource.setDriverClass(DRIVER);

                    comboPooledDataSource.setJdbcUrl(URL);

                    comboPooledDataSource.setUser(USER);

                    comboPooledDataSource.setPassword(PASSWORD);

                    comboPooledDataSource.setMaxPoolSize(100);

                    comboPooledDataSource.setMinPoolSize(10);

                    comboPooledDataSource.setInitialPoolSize(30);

                } catch (PropertyVetoException e) {

                    e.printStackTrace();

                }

            }

            public static C3p0Util getInstance() {

                if (Instance == null) {

                    return Instance = new C3p0Util();

                }

                return Instance;

            }

            public Connection getConnection() {

                try {

                    return comboPooledDataSource.getConnection();

                } catch (SQLException e) {

                    e.printStackTrace();

                }

                return null;

            }

        }

    这是在Hibernate框架中利用c3p0来循环获取链接而不关闭他们的一段代码,同时我们也输出了链接池的当前

    链接数量和循环的次数。我们发现当我们创建新的链接的时候,链接池中的链接数量增加了一个,然后我们

    再将链接的引用指向了下一个生成的链接,

    显然我们失去句柄的链接没有被垃圾回收器自动回收,因为我们连接池中的链接并没有减少。如上我们的

    代码看起来是让程序准备循环10万次的,然而,当循环进行到100次的时候,程序却不再进行任何输出,但此时程

    序依然没有终止,我们推测这是主线程进入了某种特殊的状态(等下会测试这是线程的何种状态),同时这个100

    次实际上也是在我们的控制之下的,因为我们在C3p0Util类中有这么一段代码:comboPooledDataSource.setMax

    PoolSize(100);当我们将100改成150时的时候,程序会如我们料想一样在循环150次之后才进入特殊状态的,

    但当我们再次试图将次数修改为更高的时候,却没有出现我们想要的更高次数,程序运行的次数比我们假定的

    要少,实际上我的程序每次都会在152停止,这里面显然又牵扯到了内存释放等等某些方面的问题。

    以上是第一种情况,下面会描述出第二种情况,这一个有趣的现象是对比两种情况之间的某种关系。

        import java.sql.Connection;

        import java.sql.SQLException;

        import java.util.Date;

        import java.beans.PropertyVetoException;

        import com.mchange.v2.c3p0.ComboPooledDataSource;

        public class Text {

            @SuppressWarnings({ "unused", "static-access" })

            public static void main(String[] args) {

                Long start = new Date().getTime();

                Connection conn = null;

                C3p0Util c3 = C3p0Util.getInstance();

                for (int i = 0; i < 100000; i++) {

                    conn = c3.getConnection();

                    System.out.println(i);

                    conn.close();

                    try {

                        System.out.println("这是链接池中的工作链接数"+c3.comboPooledDataSource.getNumBusyConnections());

                    } catch (SQLException e) {

                        e.printStackTrace();

                    }

                }

                Long end = new Date().getTime();

                System.out.println("耗时: " + (end - start));

            }

        }

        class C3p0Util {

            private static final String DRIVER = "com.mysql.jdbc.Driver";

            private static final String URL = "jdbc:mysql://localhost:3306/hibernate?useSSL=true&useUnicode=true&CharacterEncoding=UTF-8";

            private static final String USER = "root";

            private static final String PASSWORD = "1234";

            private static C3p0Util Instance = null;

            static ComboPooledDataSource comboPooledDataSource = null;

            private C3p0Util() {

                comboPooledDataSource = new ComboPooledDataSource();

                try {

                    comboPooledDataSource.setDriverClass(DRIVER);

                    comboPooledDataSource.setJdbcUrl(URL);

                    comboPooledDataSource.setUser(USER);

                    comboPooledDataSource.setPassword(PASSWORD);

                    comboPooledDataSource.setMaxPoolSize(100);

                    comboPooledDataSource.setMinPoolSize(10);

                    comboPooledDataSource.setInitialPoolSize(30);

                } catch (PropertyVetoException e) {

                    e.printStackTrace();

                }

            }

            public static C3p0Util getInstance() {

                if (Instance == null) {

                    return Instance = new C3p0Util();

                }

                return Instance;

            }

            public Connection getConnection() {

                try {

                    return comboPooledDataSource.getConnection();

                } catch (SQLException e) {

                    e.printStackTrace();

                }

                return null;

            }

        }        

    这段代码和第一种情况的代码只有一点小小的区别,就是在循环里面为链接对象调用了close()方法,

    但是这个方法不同于我们平常在JDBC中关闭链接调用方法,因为我们所创建的conn链接,此对象是用

    ComboPooledDataSource类中的getConnection()方法取得的connection,本质上是NewProxyConnection

    代理过的链接,在代理类里面重写了close方法,所以在我们以上代码里面调close()方法的时候,并没有

    关闭链接,仅仅是取消了代理对象的代理身份,而实际链接依然处于链接池之中,只是把链接标识回空闲

    状态,当我们仔细对比两种代

    码输出连接池中连接数的存在数量,却发现,第一情况链接数量达到了我们设置的上限后便不再变动,

    程序不再往下运行。但是在第二种情况之中,我们却发现,没当链接池中的链接数量快达到我们设定的

    最大值的时候,链接数量却莫名其妙的自动减少,于是乎链接池依旧可以容纳,于是乎程序便在一直往下执行,

    直到循环结束,这里面依然存在了某种代码实现,导致程序会自动修改链接池中的链接数(推测和close()方法的

    代码实现有关,或者close()方法会触发某种监听)。

    最后我们来通过代码检测出第一种情况的线程进入了何种状态。

        import java.sql.Connection;

        import java.sql.SQLException;

        import java.util.Date;

        import com.mchange.v2.c3p0.ComboPooledDataSource;

        import java.beans.PropertyVetoException;

        public class Text {

            @SuppressWarnings({ "unused", "static-access" })

            public static void main(String[] args) throws SQLException {

                MyThread my=new MyThread();

                my.start();

                try {

                    Thread.sleep(5000);

                } catch (InterruptedException e) {

                    // TODO Auto-generated catch block

                    e.printStackTrace();

                }

                for(;;){

                    System.out.println("my线程:"+my.getState());

                }

            }

        }

        class C3p0Util {

            private static final String DRIVER = "com.mysql.jdbc.Driver";

            private static final String URL = "jdbc:mysql://localhost:3306/hibernate?useSSL=true&useUnicode=true&CharacterEncoding=UTF-8";

            private static final String USER = "root";

            private static final String PASSWORD = "1234";

            private static C3p0Util Instance = null;

            static ComboPooledDataSource comboPooledDataSource = null;

            private C3p0Util() {

                comboPooledDataSource = new ComboPooledDataSource();

                try {

                    comboPooledDataSource.setDriverClass(DRIVER);

                    comboPooledDataSource.setJdbcUrl(URL);

                    comboPooledDataSource.setUser(USER);

                    comboPooledDataSource.setPassword(PASSWORD);

                    comboPooledDataSource.setMaxPoolSize(150);

                    comboPooledDataSource.setMinPoolSize(10);

                    comboPooledDataSource.setInitialPoolSize(30);

                } catch (PropertyVetoException e) {

                    e.printStackTrace();

                }

            }

            public static C3p0Util getInstance() {

                if (Instance == null) {

                    return Instance = new C3p0Util();

                }

                return Instance;

            }

            public Connection getConnection() {

                try {

                    return comboPooledDataSource.getConnection();

                } catch (SQLException e) {

                    e.printStackTrace();

                }

                return null;

            }

        }

        class MyThread extends Thread{

            @Override

            public void run() {

                Long start = new Date().getTime();

                Connection conn = null;

                C3p0Util c3 = C3p0Util.getInstance();

                for (int i = 0; i < 100000; i++) {

                    conn = c3.getConnection();

                    System.out.println(i);

                    try {

                        System.out.println("这是链接池中的工作链接数"+c3.comboPooledDataSource.getNumBusyConnections());

                    } catch (SQLException e) {

                        e.printStackTrace();

                    }

                    System.out.println(Thread.currentThread().isAlive());

                }

                Long end = new Date().getTime();

                System.out.println("耗时: " + (end - start));

            }

        }

    通过创建一个额外线程来完成第一二种情况里面主线程的代码,然后在循环进行到150次之后,通过主线程检测

    额外线程的状态会发现额外线程已经进入了 WAITING状态,意为无限期地等待另一个线程来执行某一特定操作的

    线程的状态。由此可见第一种情况的主线程是进入了此种状态(推测是主线程在等待连接池线派送链接,然而

    链接池已满,链接皆不为空闲状态,亦无法产生新的链接,连接池线程无法派送链接,主线程无限等待)。

本文出自小菜鸟 ________________________________危险同源 ,漏洞百出,欢迎批评指正       

继续阅读