500w 个 com.esproc.jdbc.InternalConnection 异常
正式环境运行一段时间,使用 jmap -histo:live 21864 > jmap_21864.txt 导出内存对象。
发现500w个com.esproc.jdbc.InternalConnection**** 严重异常
服务器资源 16 核,32G 内存
过了 10 分钟,又导出一个 jmap,数量没有减少,反而继续增加。
代码如下:
// 获取结果集
ResultSet rs = null;
// 建立连接
Connection con = EsProcUtils.getConnection();
String fileName = "call form-db2-page-parameter(?,?,?,?)";
if(start+limit < 1000) {
fileName = "call form-db2-page-parameter-fetch(?,?,?,?)";
}
try {
com.esproc.jdbc.InternalCStatement st = (com.esproc.jdbc.InternalCStatement) con.prepareCall(fileName);
//组装集算器第一个数组参数
String[] listIpAndDb = EsProcUtils.ipToArray(listIp, db);
st.setObject(1, listIpAndDb);//数据库名字
if(start+limit < 1000) {
BasicDBObject dataTimeSort = new BasicDBObject();
dataTimeSort.put(FormItemDataShow.DATA_TIME, -1);
st.setObject(2, EsProcUtils.runCommand(table, query, null, dataTimeSort, start+limit, start+limit));
}else {
st.setObject(2, EsProcUtils.runCommand(table, query, null, sortValue, start+limit));
}
st.setObject(3, start);
st.setObject(4, limit);
List<List<BasicDBObject>> listResult = new ArrayList<List<BasicDBObject>>();
// 执行存储过程
boolean hasResult = st.execute();
if(!hasResult) {
//此时没有结果集,无法获取,hasResult为false
return page;
}
rs = st.getResultSet();
if(rs == null) {
return page;
}
ResultSetMetaData rsmd = rs.getMetaData();
int colCount = rsmd.getColumnCount();
List<String> listColTitle = EsProcUtils.listColTitle(rsmd, colCount);
BasicDBObject basicDBObject = null;
List<BasicDBObject> list = null;
//获取列的值
while (rs.next()) {
list = new ArrayList<BasicDBObject>();
basicDBObject = EsProcUtils.resultSetToBasicDBObject(rs, colCount, listColTitle);
list.add(basicDBObject);
listResult.add(list);
}
page.setResults(listResult);
if(page.getNoTotal()!=null && page.getNoTotal()) {
}else {
EsProcUtils.close(rs, con);
Long totalPages = null;
if(start == 0 && limit > listResult.size()) {
//第一页,并且返回的数据要少limit,则任务这就是整体数据集合,不需要二次count
totalPages = 0L+listResult.size();
}else {
totalPages = countByEsproc(listIp, db, table, query);
}
if(totalPages!=null) {
page.setTotalRecords(totalPages.intValue());
}
}
} catch (Exception e) {
throw e;
}finally {
EsProcUtils.close(rs, con);
}
return page;
/**
* 关闭集算器的连接
* @param rs
* @param con
*/
public static void close(ResultSet rs, Connection con) {
if(rs!=null) {
try {
rs.close();
rs = null;
} catch (Exception e) {
log.error(“关闭游标或者链接报错”, e);
}
}
if(con!=null) {
try {
con.close();
con = null;
} catch (Exception e) {
log.error("关闭游标或者链接报错", e);
}
}
}
com.esproc.jdbc.InternalConnection 为什么这么高呢?
esProc jdbc 中没有连接池来管理和持有 Connection 对象,Connection 对象是由用户自己管理的。
EsProcUtils.getConnection() 中是怎么处理的?
我做了一个简单测试,创建 jdbc 连接并执行 SPL,执行 10000 次。
使用 jmap -histo:live PID 查看对象:
在 FINISH 打印前,InternalConnection 的 instances 数量是 1 个。
在 FINISH 打印后,InternalConnection 的 instances 数量是 0 个。
// 建立集算器连接
public static Connection getConnection() throws ClassNotFoundException, SQLException {
Class.forName(“com.esproc.jdbc.InternalDriver”);
return DriverManager.getConnection(“jdbc:esproc:local://”);
}
这个问题后续怎么排查呢?排查思路指点一下吧?
网上没搜到 live 对象的准确定义,我做个推测,垃圾收集采用标记 - 清除算法,在对象被标记为不可达(可清除)前都算是 live object,而垃圾收集线程优先级一般都比较低,大多都是在内存(也分 young 和 old 空间)不足时发生,所以可能是内存太大和代码执行又快,导致垃圾收集一直都没有执行,进而列出实际上不可达的 live object
如果要真正了解内存是否够用,可以用 jstat -gc [pid] [interval] 查看 young gc 和 full gc 的执行次数和时间
我在正式环境手动执行 System.gc()方法,多次执行,对象个数也没有减少。如果连接对象没有被引用,多次 System.gc() 应该被回收才对。
System.gc 只是建议 jvm 进行垃圾回收,并不保证立即执行垃圾回收
这个可以做测试,就是不停循环上面那段代码,看看是否会内存溢出即可,如果连的生产机,那可以换到测试机或本地
昨天晚上,对 tomcat 进行了重启。今天白天打印 jmap,发现只有 1 个对象 com.esproc.jdbc.InternalConnection。和前面差距太大。集算器和自己代码都没有升级,只是 tomcat 重启。怪!!!
今天发现 com.esproc.jdbc.InternalConnection 又异常,74w 个。
日志中发现集算器错误信息如下:
java.lang.NullPointerException
at com.esproc.jdbc.Server.getConnection(Server.java:577)
at com.esproc.jdbc.Server.removeConnection(Server.java:590)
at com.esproc.jdbc.InternalConnection.close(InternalConnection.java:782)
at com.doeis.inventory.util.EsProcUtils.close(EsProcUtils.java:1617)
线上集算器版本:
esproc-bin-20220922.jar
调用集算器代码位置如下:
建议更新下 esproc-bin.jar,大约去年 12 月优化过 esproc jdbc。