sqlite-jdbc问题反馈
2014.8.12
问题背景
在X86机上、或者龙芯上,长时间运行sqlite-jdbc,会出偶然的崩溃问题。
在不同的机器上崩溃频率不一致。龙芯做过的测试如下:
|
MIPS
|
X86
|
出错
|
- 客户: 32bit Kylin + JDK6 + sqlite3.7.15 + ClogProxy (15小时出错)
- 龙芯内部: 32bit Fedora13 + JDK6 + sqlite3.7.15 + ClogProxy (出错时间不确定 1~20小时不等)
- 32bit Fedora19 + JDK6 + sqlite3.7.15 + ClogProxy (基本稳定在两小时内出错)
|
- 客户:32bit Kylin + JDK6u45 + sqlite3.7.2 + MonitorAgent (基本稳定在两小时内出错)
- 龙芯内部:32bit Ubuntu10.04 + JDK6u45 + sqlite3.7.15 + MonitorAgent (19小时出错)
|
不出错
|
- 客户:64bitJDK 两天多不出错
- 龙芯内部: CentOS6.4 + 64bitJDK6 + sqlite3.7.15 + ClogProxy (2台*30小时不出错)
|
- 客户:
- 龙芯内部:32bit Ubuntu12.04 + JDK6u45 + sqlite3.7.15 + ClogProxy 2台*30小时不出错
- 32bit CentOS6.4 + JDK6u45 + sqlite3.7.15 + ClogProxy 2台*15小时不出错
- 64bit Fedora20 + JDK6 + sqlite3.7.15 + ClogProxy 15小时不出错
|
初步分析
- 由于此问题在X86机器上也存在,因此初步判定与龙芯CPU无关
- 而且使用最新版本X86的jdk 6也出错误,因此初步判定与龙芯Jdk无关
- 因此,问题一定出在sqlite-jdbc这个软件本身
进一步排查
- 总结两周以来全部测试崩溃日志,出错位置都是在JsonSqliteOperation.class中
- 对JsonSqliteOperation.class进行了反编译
- 仿照JsonSqliteOperation.class,专门编写了小程序,试图复现问题
[UseTest.java]
import java.sql.*;
public class UseTest extends Thread {
public static Connection conn;
public int id;
public int s = 0;;
public UseTest(int n) {
id = n;
}
public boolean hasTable(String tableName, Connection connect)
throws SQLException
{
boolean isExistTable;
DatabaseMetaData meta;
ResultSet rs;
isExistTable = false;
meta = null;
rs = null;
try
{
meta = connect.getMetaData();
if (meta != null)
{
rs = meta.getTables(null, null, tableName, null);
if (rs.next())
isExistTable = true;
if (rs != null) {
rs.close();
rs = null;
}
if (meta != null) {
meta = null;
}
}
} catch(Exception e ) {
e.printStackTrace();
}
return isExistTable;
}
public void run() {
try {
while(true) {
Statement stat = conn.createStatement();
if (hasTable("people", conn)) {
ResultSet rs = stat.executeQuery("select * from people;");
rs.close();
stat.close();
}
s++;
if (id == 0 && (s % 10000) == 0)
System.out.println(id + ":" + (s++));
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static int N = 8;
public static void main(String[] args) throws Exception {
Class.forName("org.sqlite.JDBC");
conn = DriverManager.getConnection("jdbc:sqlite:test2.db");
conn.setAutoCommit(true);
UseTest[] t = new UseTest[N];
for (int i = 0; i < N; i++)
{
t[i] = new UseTest(i);
t[i].start();
}
for (int i = 0; i < N; i ++)
{
t[i].join();
}
}
}
编译和运行
$ javac -cp ../sqlite-jdbc-3.7.15.jar UseTest.java
$ java -cp ../sqlite-jdbc-3.7.15.jar:. UseTest
重要进展:发现sqlite-jdbc的错误
- 运行上面的例子 UseTest,在很短时间内,发生内存溢出
- 经过反复排查程序,确认UseTest.java没有问题
- 检查溢出的位置,发现sqlite-jdbc-3.7.15.jar有严重的错误:MetaData.java存在内存溢出漏洞
[MetaData.java]
public synchronized ResultSet getTables() {
...
return conn.createStatement().executeQuery(sql);
}
- 在上面最后一句中,conn.createStatement()会创建一个隐含的 Stmt 对象
- 而在getTables()返回以后,再也没有机会执行 Stmt.close(),从而导致内存永远得不到回收
- 因此,无论这个内存溢出问题是否是导致崩溃问题的直接原因,这个内存溢出问题也足以导致程序无法正常运行
观察sqlite-jdbc-3.7.15.jar代码
- 查看MetaData.java头部注释信息,发现这份代码来源于一个社区项目,主要作者是David Crawshaw,最早是在zentus.com公司期间开发
- 这个代码中,大量充斥着不良设计风格,包括定义后未使用的变量getTables、不直观的sql拼接语句,等等。
- 总体感觉,这份代码质量很差,在开源项目中属于品质低下的项目,使用这份代码做自己的项目会存在较大的风险
查找sqlite-jdbc的最新代码
- 在主流代码拖管网站bitbucket.org上找到了sqlite-jdbc的活跃分支
- https://bitbucket.org/xerial/sqlite-jdbc
最新代码已经解决内存溢出问题!
* Grace Batumbya committed a32c004 2013-05-12
Fixes issue #73 DatabaseMetaData.getTables() Memory Leak
- 在这个版本中,明确的把 getTables() 加上了 Stmt.close()的处理,消除了内存溢出问题
- 而客户现在使用的sqlite-jdbc版本极为古老(对应的具体时间未知),还未进行这些错误修正,因此存在内存溢出问题,甚至还可能包含更多深层次问题
下一步工作
- 到目前为止,虽然崩溃的原因尚未清楚,但是有力的找到了低版本中内存溢出的证据,能够证明版本升级是一个必要措施
- 请客户首先升级到官方sqlite-jdbc的最新版本,再次测试崩溃问题是否存在
- 龙芯也会同步做这个测试,如果官方sqlite-jdbc的最新版本仍然存在崩溃问题,龙芯会投入力量修正官方未解决的问题,直到协助客户把系统在龙芯机器上稳定运行