Утечка памяти с JDBC / SQLite - PullRequest
0 голосов
/ 04 июля 2019

У меня есть некоторый код, который обращается к базе данных SQLite с помощью JDBC.

Я заметил, что каждый раз, когда делается запрос, использование памяти увеличивается - и оно не падает, даже после того, как соединениезакрыто.

Вот что я делаю:

1) Закрытие PreparedStatement

2) Закрытие ResultSet

3) Закрытие соединения

Вот скриншот анализа heapdump:

heap dump screenshot

Показывает много java.lang.ref.Finalizer и много PreparedStatementи ResultSet объекты.

Вот код (его в 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 ГБ до 33 ГБ и никогда не уменьшается (даже если ResultSet + PreparedStatement закрываются), и даже в конце, когда conn закрывается и поток спитв течение 1 минуты - использование памяти все еще не уменьшается.

Кто-нибудь знает, что здесь происходит?Буду признателен за любую помощь.

1 Ответ

3 голосов
/ 08 июля 2019

Мы запускаем 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/), чтобы точно понять, какие типы сборщиков мусора выполнялись для дальнейшей настройки.

Они могут не соответствовать вашему варианту использования, но вот некоторые варианты, которые вы можете рассмотреть в зависимости от того, с чем сталкиваетесь.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...