Java?內(nèi)存泄漏排查,新技能Get
| 背景
前些日子小組內(nèi)安排值班,輪流看顧我們的服務(wù),主要做一些報(bào)警郵件處理、Bug 排查、運(yùn)營 issue 處理的事。工作日還好,無論干什么都要上班的,若是輪到周末,那這一天算是毀了。
網(wǎng)絡(luò)問題?
晚上七點(diǎn)多開始,我就開始不停地收到報(bào)警郵件,郵件顯示探測的幾個(gè)接口有超時(shí)情況。多數(shù)執(zhí)行棧都在:
java.io.BufferedReader.readLine(BufferReader.java:389)
java_io_BufferedReader$readLine.call(Unknown Source)
com.domain.detect.http.HttpClient.getResponse(HttpClient.groovy:122)
com.domain.detect.http.HttpClient.this$2$getResponse(HttpClient.groovy)
這個(gè)線程棧的報(bào)錯(cuò)我見得多了,我們設(shè)置的 HTTP DNS 超時(shí)是 1s, connect 超時(shí)是 2s, read 超時(shí)是 3s,這種報(bào)錯(cuò)都是探測服務(wù)正常發(fā)送了 HTTP 請求,服務(wù)器也在收到請求正常處理后正常響應(yīng)了,但數(shù)據(jù)包在網(wǎng)絡(luò)層層轉(zhuǎn)發(fā)中丟失了,所以請求線程的執(zhí)行棧會停留在獲取接口響應(yīng)的地方。這種情況的典型特征就是能在服務(wù)器上查找到對應(yīng)的日志記錄。而且日志會顯示服務(wù)器響應(yīng)完全正常。與它相對的還有線程棧停留在 Socket connect 處的,這是在建連時(shí)就失敗了,服務(wù)端完全無感知。
| 問題爆發(fā)
本以為這次值班就起這么一個(gè)小波浪,結(jié)果在晚上八點(diǎn)多,各種接口的報(bào)警郵件蜂擁而至,打得準(zhǔn)備收拾東西過周日單休的我措手不及。
內(nèi)存泄漏
于是趕快登錄探測服務(wù)器,首先是top free df三連,結(jié)果還真發(fā)現(xiàn)了些異常。
jstat
jstat 是一個(gè)非常強(qiáng)大的 JVM 監(jiān)控工具,一般用法是:jstat [-options] pid interval
- -class 查看類加載信息
- -compile 編譯統(tǒng)計(jì)信息
- -gc 垃圾回收信息
- -gcXXX 各區(qū)域 GC 的詳細(xì)信息 如 -gcold
| 排查
問題雖然解決了,但為了防止它再次發(fā)生,還是要把根源揪出來。
分析棧
棧的分析很簡單,看一下線程數(shù)是不是過多,多數(shù)棧都在干嘛。
> 464
才四百多線程,并無異常。
10 at java.lang.Class.forName0(Native Method)
10 at java.lang.Object.wait(Native Method)
16 at java.lang.ClassLoader.loadClass(ClassLoader.java:404)
44 at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method)
344 at sun.misc.Unsafe.park(Native Method)
線程狀態(tài)好像也無異常,接下來分析堆文件。
下載堆 dump 文件
堆文件都是一些二進(jìn)制數(shù)據(jù),在命令行查看非常麻煩,Java 為我們提供的工具都是可視化的,Linux 服務(wù)器上又沒法查看,那么首先要把文件下載到本地。
使用 MAT 分析 jvm heap
MAT 是分析 Java 堆內(nèi)存的利器,使用它打開我們的堆文件(將文件后綴改為.hprof), 它會提示我們要分析的種類,對于這次分析,果斷選擇memory leak suspect。
分析代碼
找到內(nèi)存泄漏的對象了,在項(xiàng)目里全局搜索對象名,它是一個(gè) Bean 對象,然后定位到它的一個(gè)類型為 Map 的屬性。
| 小結(jié)
其實(shí)還是要反省一下自己的,一開始報(bào)警郵件里還有這樣的線程棧:
groovy.json.internal.JsonParserCharArray.decodeJsonObject(JsonParserCharArray.java:132)
groovy.json.internal.JsonParserCharArray.decodeValueInternal(JsonParserCharArray.java:186)
groovy.json.internal.JsonParserCharArray.decodeJsonObject(JsonParserCharArray.java:132)
groovy.json.internal.JsonParserCharArray.decodeValueInternal(JsonParserCharArray.java:186)
看到這種報(bào)錯(cuò)線程棧卻沒有細(xì)想,要知道 TCP 是能保證消息完整性的,況且消息沒有接收完也不會把值賦給變量,這種很明顯的是內(nèi)部錯(cuò)誤,如果留意后細(xì)查是能提前查出問題所在的,查問題真是差了哪一環(huán)都不行啊。