即使数据库已清空,HSQLDB为什么在多次插入后仍会产生OutOfMemoryError?

时间:2018-08-18 23:04:15

标签: java hsqldb

我们正在使用HSQLDB进行一系列JUnit集成测试。对于一个特定的测试,我需要加载一堆数据以验证我们在数据库上运行的某些算法。套件中的每个测试都会插入一大批,测试算法,然后删除所有记录。不幸的是,即使每次都清除所有记录并且在任何给定时间数据库中的最大记录数不变,HSQLDB最终仍将引发OutOfMemoryError。

这里是一个极简的JUnit测试,以重现此问题。如您所见,它只是插入然后删除一堆行。删除后导致错误的HSQLDB在内存中保留什么?我可以更改什么以能够无限期地运行插入删除操作(或至少有足够的能力执行所有测试)?

package mypackage;

import org.junit.Test;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * Verifying HSQLDB OutOfMemoryError
 *
 * @author Ricardo van den Broek
 */
public class HsqlDbTest {
  @Test
  public void test() throws Exception {
    executeSql("CREATE TABLE MY_TABLE (MY_COLUMN CLOB)");

    String str = "TESTTESTTESTTESTTESTTESTTESTTEST";
    String x = String.format("INSERT INTO MY_TABLE VALUES('%S')", str);

    for (int i=0;i<1000;i++){
      System.out.println("Starting batch: "+i);

      for (int j=0; j<10000; j++) {
        executeSql(x);
      }

      System.out.println("Inserted: "+getCount());
      executeSql("DELETE FROM MY_TABLE");
      System.out.println("After delete: "+getCount());
    }
  }

  private void executeSql(String sql) throws SQLException {
    Connection c = DriverManager.getConnection("jdbc:hsqldb:mem:a");
    PreparedStatement ps = c.prepareStatement(sql);
    ps.executeUpdate();
    ps.close();
    c.close();
  }

  private long getCount() throws Exception {
    Connection c = DriverManager.getConnection("jdbc:hsqldb:mem:a");
    PreparedStatement ps = c.prepareStatement("SELECT COUNT(*) FROM MY_TABLE");
    ResultSet resultSet = ps.executeQuery();

    if (resultSet.next()) {
      long count = resultSet.getLong(1);
      ps.close();
      c.close();
      return count;
    }
    throw new Exception("Should not happen");
  }
}

输出:

Starting batch: 0
Inserted: 10000
After delete: 0
Starting batch: 1
# ...
# Omitting some repetition
# ...
Inserted: 10000
After delete: 0
Starting batch: 10

java.sql.SQLException: java.lang.OutOfMemoryError: Java heap space

注意:我知道我可以增加单元测试的内存限制或在两者之间关闭数据库,但是直到有人可以向我提供合理的解释,说明这样做的原因之前,我认为我应该能够希望清空内存中的HSQLDB还应该实际上释放它正在使用的内存。

2 个答案:

答案 0 :(得分:1)

这听起来像是经典的内存泄漏,但问题是它是谁的泄漏:您的泄漏还是HSQLDB的泄漏?

我不认为这是你的。这里有一个问题:

private void executeSql(String sql) throws SQLException {
    Connection c = DriverManager.getConnection("jdbc:hsqldb:mem:a");
    PreparedStatement ps = c.prepareStatement(sql);
    ps.executeUpdate();
    ps.close();
    c.close();
}

如果抛出异常,这将泄漏准备好的语句和连接。 但是,证据并不表明您的情况正在发生。

任何...写该方法的正确方法(从Java 7开始...)是

private void executeSql(String sql) throws SQLException {
    try (Connection c = DriverManager.getConnection("jdbc:hsqldb:mem:a");
         PreparedStatement ps = c.prepareStatement(sql)) {
        ps.executeUpdate();
    }
}

那HSQLDB呢?

HSQLDB问题跟踪器中存在一个关于DELETE的内存泄漏的长期问题-请参见https://sourceforge.net/p/hsqldb/bugs/1434/。它可能相关也可能不相关。还有其他内存泄漏问题已解决。

也有可能是尚未报告为问题的HSQLDB错误。 Google找到了我其他有关HSQLDB内存泄漏的声明/报告,其中包括通过升级到较新的HSQLDB版本解决了该问题的报告。

我的建议是:

  • 如果您使用的是旧版HSQLDB,请尝试升级。
  • 如果失败,则将您的首选内存泄漏检测过程应用于此特定问题,并查看导致问题的原因。

答案 1 :(得分:1)

这不是错误。在没有文件的mem:(仅内存)数据库中,当列类型为CLOB(与VARCHAR相反)时,String对象存储在单独的内存LOB存储中,不会删除该行,因为删除了行。您需要不时执行CHECKPOINT来清除LOB存储。在每次运行结束时添加executeSQL("CHECKPOINT")

您可以将列声明为LONGVARCHAR或任何足够大的VARCHAR(n),以避免使用CHECKPOINT语句。

即使file:数据库使用文件存储LOB,即使该表是MEMORY表也不会出现此问题。在这些数据库中,CHECKPOINT释放.lobs文件中已删除块所占用的空间。然后将这些空间重新用于新的LOB。