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

因为游戏的服务端是用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 connectionPhantomRefs

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

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



你可能感兴趣的:(解决com.mysql.jdbc.NonRegisteringDriver的内存泄漏)