天天看點

com.mysql.jdbc.nonregisteringdriver_解決com.mysql.jdbc.NonRegisteringDriver的記憶體洩漏

因為遊戲的服務端是用Java編寫的,大量使用了緩存和資料異步寫入機制,但在運作的過程中要大量記錄營運日志,是以資料庫的讀寫雖然可能不像web伺服器那樣頻繁,但資料庫的開銷其實還是相對可觀的。上線營運一段時間後,伺服器越來越卡,而且還出現Out Of Memory的情況。

分析一:使用jmap指令觀察到Jvm的GC已經到了很危險的情況,JVM的新生代和老年代都幾乎已經消耗完畢了,如下圖。

com.mysql.jdbc.nonregisteringdriver_解決com.mysql.jdbc.NonRegisteringDriver的記憶體洩漏

分析二:繼續檢視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的源代碼打開,發現了裡面有一個很重要的變量

ConcurrentHashMapconnectionPhantomRefs

這個map儲存了所有連接配接的執行個體的虛引用(PhantomReference),然後由AbandonedConnectionCleanupThread不斷釋放,但最關鍵的是,似乎PhantomReference的特性是隻有當主動調用System.gc()這個方法時才會主動釋放(可能我說得不對,但據我觀察的情況确定是這樣)。而由于之前從一些jvm的調優文章裡看到建議不要主動調用System.gc(),是以我整個項目裡都從來沒主動調用過System.gc()。

com.mysql.jdbc.nonregisteringdriver_解決com.mysql.jdbc.NonRegisteringDriver的記憶體洩漏

最後,解決方法很簡單,隻要自己寫一個定時器,隔一段時間執行一下,就可以源源不斷地清理這些PhantomReference