問題場景
線上,有時候會遇到一種這樣的情況:tomcat
沒有奔潰退出,輸出日誌也沒有異常,但是界面訪問就一直卡着。假如遇到這種情況,沒錯,你遇到了tomcat
假死問題了。那麼,該怎麼排查這個問題呢?這個就是本文的重點了。
問題環境
軟件 | 版本 |
---|---|
tomcat | 7.0 |
JDK | 1.6 |
Centos | 6 |
問題原因
tomcat假死的原因有多種,這裏羅列博主遇到的幾種情況:
- HashMap死鎖
- 內存泄露
- CLOSE_WAIT過多
排查過程
遇到這種tomcat
假死的情況,先不着急重啓應用,先排查一下。
以下是博主之前操作的過程:
查看tomcat的gc情況
使用以下命令拿到對應的gc
情況,命令如下:
jmap -heap pid >> jvm_memory.log
這個命令,主要是打印堆摘要,其中包括使用的GC
算法、堆配置和生成堆使用情況等。博主生成的文件當中,關於GC
部分如下:
using thread-local object allocation.
Parallel GC with 16 thread(s)
Heap Configuration:
MinHeapFreeRatio = 40
MaxHeapFreeRatio = 70
MaxHeapSize = 3221225472 (3072.0MB)
NewSize = 2686976 (2.5625MB)
MaxNewSize = -65536 (-0.0625MB)
OldSize = 5439488 (5.1875MB)
NewRatio = 2
SurvivorRatio = 8
PermSize = 134217728 (128.0MB)
MaxPermSize = 268435456 (256.0MB)
Heap Usage:
PS Young Generation
Eden Space:
capacity = 935133184 (891.8125MB)
used = 171217136 (163.28538513183594MB)
free = 763916048 (728.5271148681641MB)
18.309385115350587% used
From Space:
capacity = 68878336 (65.6875MB)
used = 41388552 (39.47119903564453MB)
free = 27489784 (26.21630096435547MB)
60.089361043797574% used
To Space:
capacity = 67174400 (64.0625MB)
used = 0 (0.0MB)
free = 67174400 (64.0625MB)
0.0% used
PS Old Generation
capacity = 2147483648 (2048.0MB)
used = 2142115640 (2042.8806686401367MB)
free = 5368008 (5.119331359863281MB)
99.75003264844418% used
PS Perm Generation
capacity = 161087488 (153.625MB)
used = 159574696 (152.18228912353516MB)
free = 1512792 (1.4427108764648438MB)
99.06088795673566% used
從輸出結果,我們可以看到目前是在進行GC操作。
另外,我們也可以使用命令jstat
來查看,命令如下:
jstat -gcutil pid 250 7
說明:
pid: 進程號
250:間隔時間,單位爲毫秒
7: 輸出次數,這裏指輸出7次
因爲當時排查的時候,沒有使用這個命令。等問題解決了,才發現有這個命令,沒辦法復現當時的情況。
爲了給大家講解一下,就運行此命令,展示當前應用的情況:
S0 | S1 | E | O | P | YGC | YGCT | FGC | FGCT | GCT |
---|---|---|---|---|---|---|---|---|---|
0.00 | 0.00 | 82.15 | 84.94 | 99.84 | 50388 | 5413.400 | 2870 | 18355.985 | 23769.385 |
0.00 | 0.00 | 86.08 | 84.94 | 99.84 | 50388 | 5413.400 | 2870 | 18355.985 | 23769.385 |
0.00 | 0.00 | 90.86 | 84.94 | 99.84 | 50388 | 5413.400 | 2870 | 18355.985 | 23769.385 |
0.00 | 0.00 | 93.88 | 84.94 | 99.84 | 50388 | 5413.400 | 2870 | 18355.985 | 23769.385 |
0.00 | 0.00 | 97.54 | 84.94 | 99.84 | 50388 | 5413.400 | 2870 | 18355.985 | 23769.385 |
23.60 | 0.00 | 5.96 | 84.94 | 99.84 | 50389 | 5413.476 | 2870 | 18355.985 | 23769.461 |
23.60 | 0.00 | 21.19 | 84.94 | 99.84 | 50389 | 5413.476 | 2870 | 18355.985 | 23769.461 |
說明:下面是各個選項的含義
列 | 說明 |
---|---|
S0 | Heap上的 Survivor space 0 段已使用空間的百分比 |
S1 | Heap上的 Survivor space 1 段已使用空間的百分比 |
E | Heap上的 Eden space 段已使用空間的百分比 |
O | Heap上的 Old space 段已使用空間的百分比 |
P | Perm space 已使用空間的百分比 |
YGC | 從程序啓動到採樣時發生Young GC的次數 |
YGCT | Young GC所用的時間(單位秒) |
FGC | 從程序啓動到採樣時發生Full GC的次數 |
FGCT | 完整的垃圾收集時間(單位秒) |
GCT | 總垃圾收集時間(單位秒) |
排查內存泄露問題
一般應用運行了很久,突然纔出現問題,可能是有幾個原因:
- 外部原因的變化,如遠程服務異常;
- 內部原因的變化,如升級代碼存在問題等。
一般針對內存泄露,除了開發代碼的時候,就進行規範之後,等運行之後,要查看該問題,特別是線上環境的話,最好將完整的JVM堆棧信息
給dump
下來。這個時候可以使用以下命令:
jmap -dump,format=b,file=heap-dump.bin <pid>
這個命令的作用爲:將hprof
二進制格式的Java
堆轉儲到filename
。live
子選項是可選的。如果指定,則只轉儲堆中的活動對象。要瀏覽堆轉儲,可以使用jhat(Java堆分析工具)
讀取生成的文件。
轉換的文件比較大,一般有幾個GB。所以要從現場環境拿下來,肯定得先進行壓縮,然後再下載。這裏我推薦使用JProfiler
來進行JVM分析。關於JProfiler
,請自行搜索使用。
使用JProfiler
打開head-dump.bin
文件,就可以看到一個很明顯的東西,截圖如下:
在上圖可以知道,在Classes
頁面,有一個類路徑下面,生成了很多對象。我們點擊Biggest Objects
,截圖如下:
從上圖可以知道,有一個對象非常大,佔據了1487MB
。而從第一步拿到的堆棧信息,整個堆棧最大是3072.0MB
。所以,初步可以判斷是因爲這個東西,導致了頻繁的GC
,進行導致tomcat
假死。
通過諮詢項目運維成員,說該對象屬於以前的插件-聽雲,屬於端對端監控的插件。該插件之前要下線的,但是沒有執行成功。所以該插件目前還運行着。而最近聽雲服務器正式下線,所以導致tomcat
連接聽雲出現問題。暫時鎖定該問題,所以先安排運維人員移除該插件,並重啓tomcat
。其中也遇到一個問題,並形成博客《問題解決:啓動tomcat,日誌輸出:java.lang.ClassNotFoundException: com.tingyun.api.agent.TingYunApiImpl》,有興趣的小夥伴可以看看。
重啓之後,目前運行將近一週了,還未發現tomcat假死
現象。所以,可以肯定,這個插件是造成近期應用假死的元兇。
這個原因是屬於近期頻繁假死的真兇,但是運維人員反映,該應用在之前是會出現這種tomcat假死
的情況的。只是頻率不會像現在一天兩次的情況,一般是數週會發生一次。所以,還是得繼續排查。
HashMap死鎖
在應用運行一段時間之後,使用阿里巴巴開源的arthas來進行問題排查。這個是排查Java
問題的利器。關於arthas
的使用,請自行登錄官網查看。
登錄機器,使用arthas
連接應用,查看當前的線程情況,發現有意思的一幕,截圖如下:
有兩個線程一直掛着,沒有退出過。通過命令thread
查看該線程具體情況,截圖如下:
如果曾經遇到過這個問題的話,應該可以知道,這個就是所謂的HashMap死鎖
問題。這個需要去查看具體的代碼,查看爲什麼會發生死鎖的問題。
從這個可以得到,假如這個HashMap死鎖
發生的頻率不高,但是隨着時間的推移,這種情況不斷髮生,就會將全部的連接給佔據了,導致了tomcat
沒可用連接進行響應,就會導致tomcat假死
的情況發生。這個就是爲什麼之前應用發生死鎖的頻率比較低,要數週纔可能會發生一次。
CLOSE_WAIT過多情況
在實際場景,我們也會發現一種情況,tomcat假死
的時候,使用以下命令獲取TCP
連接情況:
netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
結果如下(命令可能和截圖不同,因爲這個是進行篩選並定時執行得到的):
可以看到,目前存在很多的CLOSE_WAIT
。這個是因爲服務端未及時釋放資源。關於這個問題,除了可以修改Linux
配置,我們也可以直接修改tomcat
的配置文件,禁用wait
,讓連接在返回後立馬關閉,成爲一個可用連接。新增選項:
<Connectorport=“11011” protocol=“org.apache.coyote.http11.Http11NioProtocol” connectionTimeout=“20000” maxThreads=“1000” URIEncoding=“UTF-8” keepAliveTimeout=“0” />
其中的keepAliveTimeout="0"
是解決這個問題的關鍵。這個參數在其中一個應用進行試驗,原本經常有CLOSE_WAIT
,新增之後,基本沒有,而且也沒有報錯。
總結
通過實際的現場場景,展示了整個排查的過程,中間涉及jmap
、jstack
、jstat
、arthas
、jprofiler
等命令及軟件。熟練地使用這些命令,可以得到很多有用的信息,並加以解決。
參考鏈接
jmap官方說明文檔
jstack官方說明文檔
jstat官方說明文檔
arthas官方文檔
參考圖
JVM結構
TCP的三次握手和四次揮手
隨緣求贊
如果我的文章對大家產生了幫忙,可以在文章底部點個贊或者收藏;
如果有好的討論,可以留言;
如果想繼續查看我以後的文章,可以點擊關注
可以掃描以下二維碼,關注我的公衆號:楓夜之求索閣,查看我最新的分享!