JDBC / SQLite的内存泄漏

时间:2019-07-04 08:32:40

标签: java scala sqlite jdbc memory-leaks

我有一些代码可以使用JDBC访问SQLite数据库。

我注意到,每次进行查询时,内存使用量都会增加-即使关闭连接后,内存使用量也不会下降。

这就是我在做什么:

1)关闭PreparedStatement

2)关闭ResultSet

3)关闭连接

这是heapdump分析的屏幕截图:

heap dump screenshot

它显示了很多java.lang.ref.Finalizer和很多PreparedStatementResultSet对象。

这是代码(在scala中,但应该可以与Java轻松比较):

val conn: Connection = DriverManager.getConnection(url)


// Gets strings by a query like SELECT .. WHERE foo = ?
def getStringsByQuery(query: String, param: String, field: String):Seq[String] = {

    val st = conn.prepareStatement(query)
    st.setString(1, param) //value of foo = ?
    st.setFetchSize(Integer.MAX_VALUE)
    st.setMaxRows(Integer.MAX_VALUE)

    //Holder of results
    var results = collection.mutable.Seq.empty[String]

    val rs: ResultSet = st.executeQuery()

    //add results to holder
    while (rs.next())
      results :+= rs.getString(field)

    rs.close() //closing ResultSet
    st.close() //closing PreparedStatement
    results
  }

这是我为此编写的测试:

test("detect memory leak") {

    log.info("Starting in 10 sec")
    Thread.sleep(10.seconds.toMillis)

    //Calls a method over and over to see if there's a memory leak or not..
    (1 to 1000).par.foreach(i => {
      val randomWord = getRandomWord() //this produces a random word
      val sql = "SELECT foo FROM myTable where bar = ?"
      val results = getStringsByQuery(sql, randomWord, "bar")
    })

    conn.close() //close the connection
    log.info("Closed conn, closing in 30 sec")

    Thread.sleep(1.minutes.toMillis)
  }

运行测试时-内存使用量从24.6 GB稳定增加到33 gb,并且从未下降(即使ResultSet + PreparedStatement被关闭),甚至在结束时关闭conn并且线程休眠1分钟-内存使用率仍然没有下降。

有人知道这是怎么回事吗?我将不胜感激。

3 个答案:

答案 0 :(得分:3)

我们在生产环境中运行sqlite-jdbc,并且在检查堆转储时注意到类似的行为。

这些对象仅在堆上,因为它们尚未通过垃圾收集器运行。在https://blog.nelhage.com/post/three-kinds-of-leaks/的意义上,这是“类型2内存泄漏”,因为对象已分配并且寿命比您预期的长。

这对我们造成的问题是,机器将在Java堆上保留内存意外长的时间,从而在高峰请求时间内陷入内存压力。我们做了一些不同的事情,以确保更频繁地收集这些对象。

  • 减小整体Java堆大小。这样可以确保垃圾收集器看到清理这些对象的更多需求。我们在SQLite上的工作量非常大,因此最好将计算机内存通过JNI转到堆外内存,而不是分配给Java堆。
  • 调整G1GC垃圾收集器以为newgen分配更多的堆,因为这些对象通常是非常短命的。 (https://www.oracle.com/technetwork/articles/java/g1gc-1984535.html
  • 增加垃圾收集器的日志记录,并通过GCEasy(https://gceasy.io/)之类的服务运行它们,以准确了解正在运行什么类型的垃圾收集器以进行进一步调整。

这些可能与您的用例不符,但是您可以根据所遇到的情况考虑一些选项。

答案 1 :(得分:0)

我关闭了连接和数据库对象,创建了一个具有相同参数和新连接的新对象数据库,最后调用了垃圾收集。

根据需要重复该过程。当我用完计算机内存的一半时,我会做一个计数器来重复该过程。

现在我可以正常运行了。

答案 2 :(得分:-1)

我通过将Sqlite数据集移植到CQEngine解决了此问题。再开心不过了。

相关问题