Java内存泄漏与数据源

问题描述

我多次听到关闭数据库连接可能会导致内存泄漏
(例如,在此article中)

我通过从 org.apache.commons.dbcp2.BasicDataSource
获得连接来尝试再现相同的问题 而不是关闭它。

这是我的代码

//Repo class
private final BasicDataSource ds;
public Repo() {
    ds = new BasicDataSource();

    ds.setDriverClassName("org.postgresql.Driver");
    ds.setUrl("jdbc:postgresql://localhost/postgres");
    ds.setUsername("postgres");
    ds.setPassword("postgres");
    ds.setMaxOpenPreparedStatements(10000);
    ds.setMaxTotal(10000);


}
public PreparedStatement prepStatement(String sql) throws sqlException {
    return this.ds.getConnection().prepareStatement(sql);
}

//Logic class
public JsonNode logic(String name) {
    PreparedStatement ps = this.repo.prepStatement("select data from public.users where name = ? ");
    ps.setString(1,name);
    //The rest of the logic - no close method or try with resource
}

我已经重复了差不多400次相同的过程,并且还减小了初始堆大小和最大堆大小。
但是,没有迹象表明存在内存泄漏。即使在VisualVM监视中,堆图也看起来很正常:

enter image description here

关于如何重制此问题的任何想法?

解决方法

不关闭资源的问题不是潜在的内存泄漏,而是潜在的 resource 泄漏。我们谈论的是文件句柄,网络连接,甚至是可能在数据库服务器端分配的资源,这些资源在JVM中根本不可见。

取决于特定的数据库驱动程序,实际的PreparedStatement实现是否具有诸如终结器或清理器之类的保护措施,该保护措施将在对象成为垃圾回收对象时关闭资源。但是,即使有,这也意味着资源将被保留,直到随后的垃圾回收周期识别出不可达的对象并触发完成为止。

在您的特定设置中,似乎每分钟都有一个垃圾收集。也许在那时这些关键的非内存资源被清理掉了。您甚至都没有检查它们。

但是,即使为此设置清理了这些资源,您也必须注意以下几点:

  • 并非每个数据库驱动程序都可以这种方式工作

  • 在实际的生产环境中,将关键的非内存资源(例如锁,文件句柄,数据库连接等)保留比所需的时间长一分钟的时间已经成为一个大问题。

  • 无法保证您每分钟都有垃圾收集。系统可能会运行数小时甚至数天而没有垃圾收集。

  • 没有保证垃圾收集可以标识特定的不可访问对象。在下一次收集发生时对象属于“年轻一代”的简单设置中,此操作可能会顺利进行,但是现代并发收集器很乐意在较短的可配置时间限制内回收大量内存,而不必急于收集每个对象

    就内存而言,每个对象的内存都是相等的,因此无关紧要,哪些对象被此类“最大的花销”忽略了。因此,PreparedStatement实例可能是每个集合中忽略的不幸的不可访问对象之一。几个字节都没关系,它会阻塞,因此才允许使用此策略。如上所述,问题在于它可能无限期持有的非内存资源。