通常程序员都要通过调用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状态,意为无限期地等待另一个线程来执行某一特定操作的
线程的状态。由此可见第一种情况的主线程是进入了此种状态(推测是主线程在等待连接池线派送链接,然而
链接池已满,链接皆不为空闲状态,亦无法产生新的链接,连接池线程无法派送链接,主线程无限等待)。
本文出自小菜鸟 ________________________________危险同源 ,漏洞百出,欢迎批评指正