前两天两个Bea公司的来做技术支持,说我们的代码有问题导致了内存泄露,让我们在rs.close()和stmt.close()后面一定要加上rs = null和stmt = null。

爆寒!

>让我们在rs.close()和stmt.close()后面一定要加上rs = null和stmt = >null。

没有这种解决方法吧?太逗了。

是好象没这种必要

Note: A ResultSet object is automatically closed by the Statement object that generated it when that Statement object is closed, re-executed, or is used to retrieve the next result from a sequence of multiple results. A ResultSet object is also automatically closed when it is garbage collected.

Note: A Statement object is automatically closed when it is garbage collected. When a Statement object is closed, its current ResultSet object, if one exists, is also closed.

Note: A Connection object is automatically closed when it is garbage collected. Certain fatal errors also close a Connection object.

摘自JDK1.4

可见
1.垃圾回收机制可以自动关闭它们
2.Statement关闭会导致ResultSet关闭
3.Connection关闭不会(?)导致Statement关闭
4.由于垃圾回收的线程级别是最低的,为了充分利用数据库资源,有必要显式关闭它们,尤其是使用Connection Pool的时候。
5.最优经验是按照ResultSet,Statement,Connection的顺序执行close
6.如果一定要传递ResultSet,应该使用RowSet,RowSet可以不依赖于Connection和Statement。Java传递的是引用,所以如果传递ResultSet,你会不知道Statement和Connection何时关闭,不知道ResultSet何时有效。

我是这样处理的:在 DAO Factory 里放上这个方法。BaseImpl 即所有DAO 接口对象的实现的基类里,调用这个方法。另外还有 getConnection 的。这样,在我的每个 Impl 类中,在用完这些资源后,即 try 的最后,都直接使用 close(rs, pst, conn) 来关闭相关数据库资源(因为是传引用哦,所以我是觉得可以这样来减少代码)。getConnection() 是直接得到连接啦。

请大家帮我评判一下,这样处理有问题吗?


public static void close(java.sql.ResultSet rs,
java.sql.PreparedStatement pst,
java.sql.Connection conn
) {
if (rs != null) {
try {
rs.close();
rs = null;
} catch (SQLException se) {
se.printStackTrace();
} finally {
if (rs != null) {
try {
rs.close();
} catch (SQLException se) {
se.printStackTrace();
}
rs = null;
}
}
}

if (pst != null) {
try {
pst.close();
pst = null;
} catch (SQLException ex) {
ex.printStackTrace();
} finally {
if (pst != null) {
try {
pst.close();
} catch (SQLException se) {
se.printStackTrace();
}

pst = null;
}
}
}

if (conn != null) {
try {
conn.close();
conn = null;
} catch (SQLException ex) {
ex.printStackTrace();
} finally {
if (conn != null) {
try {
conn.close();
} catch (SQLException se) {
se.printStackTrace();
}
conn = null;
}
}
}
}
</code>

上面有人说 close 方法必须放在 finally 里,不知道基于什么考虑。

数据库资源与普通 Object 不一样的。应该说建议还是要即时、完全地关闭,也有利于 pooling 来工作。 我想是这样,应该说你的 close() 是由 pooling 的实

其实好像这是一个很普遍的误解。好多老手,高手,都想当然地rs=null。
有些jvm的实现,局部block out of scope时,局部变量引用的对象并没有变成gc'able。
不过,函数退出时是没问题的。不需要rs=null

rs=null的不好之处在于,一,麻烦。二,无法用final,而final对提高程序质量还是很有意义的。

对这个资源问题,我前几天在搞vb4(惭愧呀,都老掉牙了!)的时候,也遇到了。惊人地一致,我也是想做这么一个公用函数,来封装常见的stored proc调用。查了好久msdn也没查出个所以然来。测试倒是没问题。好在那个程序就是客户端的,没有server端那么频繁调用。

但是在vb里有问题的,到java里是可以轻松解决的。
单纯从问题本身,我认为这是标准所没有规定的。(connection关闭后Statement不可用不表示资源必然被回收)。标准没有的,各个driver的实现就可能不同。所以,无论实验结果如何,都是不可依赖的。
解决方法应该是象前面一位老兄说的,自己封装ResultSet。
感谢jdbc的设计者,ResultSet是一个接口。我们大可以自己实现ResultSet,在close里面同时关上Statement。

为了避免写好多委托函数的麻烦(ResultSet的方法好多啊!不同的jdbc版本还不一样),可以用dynamic proxy。(具体方法我本来写了一下,但是刚才提交的时候,session timeout,重新登陆后,我写的东西都不见了!懒得再写了)。
这样在你最原始的那个函数里,你可以这样用:


Statement stmt = ...;
boolean ok = false;
try{
ResultSet rs = ...;
ok = true;
return MyResultWrapper.instance(rs,stmt);
}
finally{
if(!ok)stmt.close();
}

不过,越过这个问题本身。我觉得,返回ResultSet也许不总是一个好的选择。为什么不接受一个callback呢?这样,你根本不用要求客户代码记得调用close(),一切你都处理好了。我在以前的一个帖子中也提过这种方法:

interface ResultListener{
public void acceptResult(ResultRow rs);
}
void populate(ResultListener l){
final Statement stmt = ...;
try{
final ResultSet rs = ...;
try{
while(...){l.acceptResult(new ResultRow(rs));}
}
finally{
rs.close();
}
}
finally{
stmt.close();
}
}

个人觉得,这种方法对不需要update,不需要在result set上随机移动cursor的需求够用了。而且资源管理更健壮。


最好的办法是,在执行Connection类方法close()时,同时关闭Statement,可以考虑使用Delegate模式,以牺牲部分的性能来换取
稳定;

我觉得Connection这样的对象本身就是一种封装,不同的厂商实现不同,如果使用了某个厂商的连接池,比如weblogic的,那么Connection.close()并没有释放这个conn,而是将它重新放入池中。因为从datasource中取出的conn实际上是Connection的子类,它覆盖了close()方法。具体可以查看相关厂商提供的文档。

我想这是为何有的人一定要显示关闭statement,有的人又发现不显示关闭statement也可以的原因吧。

mysapphire, 我说说你对问题看法的两个疑问:1,假设DBMS可以释放处理链接中的资源,我们就可以丝毫不去释放这些资源么?从理论上讲,这的确可以,但是实际上这并不不好(注意,是不好而不是不行),所以在JDBC编程的时候,要求程序员必须释放数据库链接,但是建议释放Statement和ResultSet链接。这就体现了问题的重要性。2.每一个DBMS都不会为这种简单的问题所弄崩溃的,甚至说包括一个好一些的链接池程序实现都会强制释放资源的(不然岂不是陷入死锁?)。
我们为什么需要释放statement和resultset资源,其实是相当地简单,第一,我们需要释放这些资源中缓存的数据(比如数据集中的数据,以及SQL语句缓存等),直接使用链接关闭是做不到这一点的(有兴趣的朋友可以采用大数据量的尝试,并用性能监控工具查看就知道了),它只是将statement和resultset的相关标识位(closed)置true。第二,在我们一般开发中,数据库链接都是采用链接池的方式,而一个链接一般JDBC驱动程序实现可以支持几十至上百个不等的Statement,如果每一个都不释放,那么最终会出现访问缓慢甚至阻塞的情况,性能急剧降低。第三,关闭Statement和ResultSet,JDBC程序会通知DBMS清除相应缓存(记住是通知而不是执行,否则效率就很低了),从而实现资源不会被长期占用(虽然DBMS会有策略进行释放,但那是被动的)。

你们BEA来的技术支持人员简直是搞笑。首先说,内存出现泄漏,根本不可能是rst没有关闭的原因,可以想想,在一个系统中使用的rst的实例能有多少个?分明是头疼医脚!一般说来,内存出现泄漏,是应该出现在大数据存储,比如说集合,很多程序员喜欢用setList等设置集合的方法,这是非常错误的,也是性能下降的一个关键(hibernate除外,下次我可以讲讲它为什么需要这样)。
其次,对于内存释放,也是比较搞笑的。在java中有两个重要的引用关系:强引用、弱引用。简单地讲吧,强引用是可以解释为全局变量,弱引用可以解释为实参或者局部变量。而java的回收,对于强引用是不能回收的(除非它为Null),而对于弱引用,不管它是否有知,都可以进行回收,而回收了,也并不意味着它不能再使用(回收机制会将对象转储到IO文件中,而在内存中只保留映射)。从楼上的几个问题看,如果是强引用,那么你设置为null,就有可能在别人使用的时候出现空指针异常(如果想释放,应该实现finalize方法);如果是弱引用,那么你就是设置为null,都没有什么太大的作用,形式罢了。

测试论坛

好久没上来了,发现很多人提出了很有针对性的见解,挺感谢大家的。
JDBC编程是比较原始的数据库访问技术,在系统的性能体现中承担着瓶径的角色,所以针对每一个JDBC功能的实现,不管有多少种方法,我们都应该(或者说必须)选择最为高效的一种。而不能象WEB展现层那样可以随意改写。 否则随着系统功能的日益繁杂和数据吞吐量的增加,
当初我发这个帖子的初衷是在自己写的一个JDBC通用功能封装的时候出现了点儿矛盾,在保持程序高效和健康的同时又想实现最大限度的代码复用,这些问题本来很好解决,直接释放就可以,但是为了封装,在我写的这些通用的类中,资源引用的传递和释放都需要一些古怪的要求,不过现在都已经解决!
现在的讨论已经超出了我所要的目的,不过挺好,让大家了解到很多应该知道的数据库内部的知识。

我的项目现在总是出现内存溢出的问题,之前,我看了一些java gc方面的资料。java的回收机制并不光是按时间的标准来做的,还有jvm的容量大小等等,反正听sun公司的工程师们介绍说,在任何情况下,都不需要手动的在java代码里进行gc。更不要考虑gc的回收机制的问题。
虽然,我的项目直到现在仍然出现内存溢出的问题,可是我想应该不是回收机制的问题,而是其它问题。
回到主题,在我们java程序代码中,创建了不管是connection对象,还是ResultSet对象,或者是statement对象,对于java的jvm来说,都会在jvm中分配空间,楼上有人说过,connection.close()会自动导致statement.close和resultset.close(),那么我想这里的connection.close()、statement.close、resultset.close()只是通知dbms可以回收相应的数据库资源,而不是释放掉jvm中的资源是吗?jvm中的相应内存资源仍然需要gc来处理,当然,如果写了connection=null,resultset=null,statement=null更好。
如果程序是通过连接池,当然一定要在代码中显示的将connection、热sultset、statement关闭。如果不关闭,不但jvm中的资源不会被gc,数据库的相应资源同样的不到释放。不知道,对不对,还请高手们探讨。

记住,全部要关闭!

嗯,我也认为关闭是有一起关闭了。如果没有连接了,你还能查询或者做其他工作吗?我想是不可能的。我们说的connection、statement、resultset都是java对象,那connection关闭了只是跟数据库交互切断了。至于在本地上的对象还存不存在并没有关系的。像上面有朋友说进行resultset对象的迭代还有数据也是正常的,你又没有resultset对象置为null.关闭而已!