解决com.mysql.jdbc.NonRegisteringDriver的内存泄漏

Java游戏服务器内存泄漏排查

因为游戏的服务端是用Java编写的,大量使用了缓存和数据异步写入机制,但在运行的过程中要大量记录运营日志,所以数据库的读写虽然可能不像web服务器那样频繁,但数据库的开销其实还是相对可观的。上线运营一段时间后,服务器越来越卡,而且还出现Out Of Memory的情况。

分析一:使用jmap命令观察到Jvm的GC已经到了很危险的情况,JVM的新生代和老年代都几乎已经消耗完毕了,如下图。

分析二:继续查看JVM 的GC日志,下面摘抄几条:

2014-05-16T13:57:23.958+0800:592015.629: [Full GC 1948862K->1943518K(2024256K), 0.8818560 secs]

2014-05-16T13:58:10.682+0800: 592062.352:[Full GC 1948862K->1943638K(2024256K), 1.0139730 secs]

2014-05-16T13:59:18.427+0800: 592130.097:[Full GC 1948862K->1943686K(2024256K), 0.9705680 secs]

FULL GC出现的情况越来越频繁, 到了最后隔几秒就要Full GC一次,而且所需要的时间越来越长,Minor GC 已经几乎不出现了,与上图中的情况完全吻合。

分析三:使用jmap-dump:format=b,file=logic_heap.hprof pid这个命令,dump出内存的映像状态文件,再使用MemoryAnalyzer这个工具分析。发现有大量的com.mysql.jdbc.NonRegisteringDriver这个对象产生泄漏。这个类是MySQL的connector/J官方驱动,按道理是不用出大问题的,我一开始也是从来没怀疑过,而且也不想去碰里面的代码。于是一直分析mybatis的代码,再分析自己封装的dao层。到了最后,实在没办法了,只能把connector/J的源代码打开,发现了里面有一个很重要的变量

ConcurrentHashMap<ConnectionPhantomReference,ConnectionPhantomReference> connectionPhantomRefs

这个map保存了所有连接的实例的虚引用(PhantomReference),然后由AbandonedConnectionCleanupThread不断释放,但最关键的是,似乎PhantomReference的特性是只有当主动调用System.gc()这个方法时才会主动释放(可能我说得不对,但据我观察的情况确定是这样)。而由于之前从一些jvm的调优文章里看到建议不要主动调用System.gc(),因此我整个项目里都从来没主动调用过System.gc()。

最后,解决方法很简单,只要自己写一个定时器,隔一段时间执行一下,就可以源源不断地清理这些PhantomReference



The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server. at com.mysql.cj.jdbc.exceptions.SQLError.createCommunicationsException(SQLError.java:174) at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:64) at com.mysql.cj.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:829) at com.mysql.cj.jdbc.ConnectionImpl.<init>(ConnectionImpl.java:449) at com.mysql.cj.jdbc.ConnectionImpl.getInstance(ConnectionImpl.java:242) at com.mysql.cj.jdbc.NonRegisteringDriver.connect(NonRegisteringDriver.java:198) at com.zaxxer.hikari.util.DriverDataSource.getConnection(DriverDataSource.java:138) at com.zaxxer.hikari.pool.PoolBase.newConnection(PoolBase.java:364) at com.zaxxer.hikari.pool.PoolBase.newPoolEntry(PoolBase.java:206) at com.zaxxer.hikari.pool.HikariPool.createPoolEntry(HikariPool.java:476) at com.zaxxer.hikari.pool.HikariPool.checkFailFast(HikariPool.java:561) at com.zaxxer.hikari.pool.HikariPool.<init>(HikariPool.java:115) at com.zaxxer.hikari.HikariDataSource.getConnection(HikariDataSource.java:112) at org.hibernate.engine.jdbc.connections.internal.DatasourceConnectionProviderImpl.getConnection(DatasourceConnectionProviderImpl.java:122) at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator$ConnectionProviderJdbcConnectionAccess.obtainConnection(JdbcEnvironmentInitiator.java:180) at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.initiateService(JdbcEnvironmentInitiator.java:68) at org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.initiateService(JdbcEnvironmentInitiator.java:35) at org.hibernate.boot.registry.internal.StandardServiceRegistryImpl.initiateService(StandardServiceRegistryImpl.java:101) at org.hibernate.service.internal.AbstractServiceRegistryImpl.createService(AbstractServiceRegistryImpl.java:263) at org.hibernate.service.internal.AbstractServiceRegistryImpl.initializeService(Abstrac
03-12
Caused by: java.sql.SQLNonTransientConnectionException: Could not create connection to database server. Attempted reconnect 3 times. Giving up. at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:111) at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:98) at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:90) at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:64) at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:74) at com.mysql.cj.jdbc.ConnectionImpl.connectWithRetries(ConnectionImpl.java:885) at com.mysql.cj.jdbc.ConnectionImpl.createNewIO(ConnectionImpl.java:810) at com.mysql.cj.jdbc.ConnectionImpl.<init>(ConnectionImpl.java:438) at com.mysql.cj.jdbc.ConnectionImpl.getInstance(ConnectionImpl.java:241) at com.mysql.cj.jdbc.NonRegisteringDriver.connect(NonRegisteringDriver.java:189) at com.zaxxer.hikari.util.DriverDataSource.getConnection(DriverDataSource.java:138) at com.zaxxer.hikari.pool.PoolBase.newConnection(PoolBase.java:354) at com.zaxxer.hikari.pool.PoolBase.newPoolEntry(PoolBase.java:202) at com.zaxxer.hikari.pool.HikariPool.createPoolEntry(HikariPool.java:473) at com.zaxxer.hikari.pool.HikariPool.checkFailFast(HikariPool.java:554) at com.zaxxer.hikari.pool.HikariPool.<init>(HikariPool.java:115) at com.zaxxer.hikari.HikariDataSource.getConnection(HikariDataSource.java:112) at com.alibaba.nacos.persistence.utils.ConnectionCheckUtil.checkDataSourceConnection(ConnectionCheckUtil.java:34) ... 66 common frames omitted Caused by: com.mysql.cj.exceptions.CJCommunicationsException: Communications link failure The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server. at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.lang.reflect.Constructor.newInstance(Constructor.java:423) at com.mysql.cj.exceptions.ExceptionFactory.createException(ExceptionFactory.java:61) at com.mysql.cj.exceptions.ExceptionFactory.createException(ExceptionFactory.java:104) at com.mysql.cj.exceptions.ExceptionFactory.createException(ExceptionFactory.java:149) at com.mysql.cj.exceptions.ExceptionFactory.createCommunicationsException(ExceptionFactory.java:165) at com.mysql.cj.protocol.a.NativeSocketConnection.connect(NativeSocketConnection.java:88) at com.mysql.cj.NativeSession.connect(NativeSession.java:120) at com.mysql.cj.jdbc.ConnectionImpl.connectWithRetries(ConnectionImpl.java:829) ... 78 common frames omitted Caused by: java.net.SocketTimeoutException: connect timed out at java.net.PlainSocketImpl.socketConnect(Native Method) at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350) at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206) at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188) at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392) at java.net.Socket.connect(Socket.java:607) at com.mysql.cj.protocol.StandardSocketFactory.connect(StandardSocketFactory.java:153) at com.mysql.cj.protocol.a.NativeSocketConnection.connect(NativeSocketConnection.java:62) ... 80 common frames omitted
最新发布
11-06
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值