一、问题描述
上周五晚上主营出现部分设备掉线,经过查看日志发现是由于缓存系统出现长时间gc导致的。这里的gc日志的特点是:
1.gc时间都在2s以上,部分节点甚至出现12s超长时间gc。
2.同一个节点距离上次gc时间间隔为普遍为13~15天。
然后紧急把剩余未gc的一个节点内存dump下来,使用mat工具打开发现,com.mysql.jdbc.NonRegisteringDriver 对象占了堆内存的大部分空间。
查看对象数量,发现com.mysql.jdbc.NonRegisteringDriver$ConnectionPhantomReference 这个对象堆积了10140 个。
初步判断长时间gc的问题应该是由于 com.mysql.jdbc.NonRegisteringDriver$ConnectionPhantomReference 这个对象大量堆积引起的。
二、问题分析
目前正式环境使用数据库相关依赖如下:
依赖
版本
mysql
5.1.47
hikari
2.7.9
Sharding-jdbc
3.1.0
根据以上描述,提出以下问题:
1、com.mysql.jdbc.NonRegisteringDriver$ConnectionPhantomReference 到底是个什么对象呢?
2、这种对象为什么会大量堆积,JVM回收不过来了?
NonRegisteringDriver$ConnectionPhantomReference 到底是个什么对象呢?
简单来说,NonRegisteringDriver类有个虚引用集合connectionPhantomRefs用于存储所有的数据库连接,NonRegisteringDriver.trackConnection方法负责把新创建的连接放入connectionPhantomRefs集合。源码如下:
1.public class NonRegisteringDriver implements java.sql.Driver {
2. protected static final ConcurrentHashMap connectionPhantomRefs = new ConcurrentHashMap();
3. protected static final ReferenceQueue refQueue = new ReferenceQueue();
4.
5. ....
6.
7. protected static void trackConnection(Connection newConn) {
8.
9. ConnectionPhantomReference phantomRef = new ConnectionPhantomReference((ConnectionImpl) newConn, refQueue);
10. connectionPhantomRefs.put(phantomRef, phantomRef);
11. }
12. ....
13.}
我们追踪创建数据库连接的过程源码,发现其中会调到com.mysql.jdbc.ConnectionImpl的构造函数,该方法会调用createNewIO方法创建一个新的数据库连接MysqlIO对象,然后调用我们上面提到的NonRegisteringDriver.trackConnection方法,把该对象放入NonRegisteringDriver.connectionPhantomRefs集合。源码如下:
1.public class ConnectionImpl extends ConnectionPropertiesImpl implements MySQLConnection {
2.
3. public ConnectionImpl(String hostToConnectTo, int portToConnectTo, Properties info, String databaseToCon