結(jié)算系統(tǒng)上線后盛末,每到月初月末弹惦,都有點(diǎn)膽戰(zhàn)心驚,最怕聽到“某某某悄但,我這個(gè)下載又不行”棠隐、“我這個(gè)都下載了20分鐘了,怎么還不行八隳宵荒!”...... 我能怎么辦哇,停下來把鍋撿起來唄净嘀。
撿鍋記之檢鍋
撿鍋了报咳,然后呢?當(dāng)然是查一查問題出在哪了挖藏。ssh上服務(wù)器暑刃,先說說服務(wù)器配置吧。這臺(tái)服務(wù)器是在某離職大神的建議下購(gòu)買的膜眠,配置還不錯(cuò)岩臣。單核SSD硬盤
,其他配置如下:
[web@monitor ~]$ free -m
total used free shared buff/cache available
Mem: 3790 2467 173 0 1150 1021
Swap: 0 0 0
[web@monitor ~]$ cat /etc/redhat-release
CentOS Linux release 7.3.1611 (Core)
查看服務(wù)狀態(tài)
記得當(dāng)初車險(xiǎn)系統(tǒng)剛升級(jí)SpringBoot的時(shí)候宵膨,經(jīng)常發(fā)現(xiàn)系統(tǒng)掛掉架谎,Java進(jìn)程也被kill掉,還糾結(jié)了好久辟躏,排查了很久發(fā)現(xiàn)是被由于Linux的OOM Killer機(jī)制殺掉的谷扣。
Linux OOM_killer是Linux自我保護(hù)的方式,當(dāng)內(nèi)存不足時(shí)不至于出現(xiàn)太嚴(yán)重問題捎琐,有點(diǎn)壯士斷腕的意味会涎。在kernel 2.6,內(nèi)存不足將喚醒oom_killer瑞凑,挑出/proc/<pid>/oom_score最大者并將之kill掉末秃,可以把/proc/<pid>/oom_score_adj值改小(最小-17)來臨時(shí)避免此種情況籽御。
所以出現(xiàn)服務(wù)停止后练慕,我第一反應(yīng)就是查看Java進(jìn)程還在不在惰匙。
[web@monitor ~]$ jcmd
19335 settlement.jar --spring.profiles.active=prod
30142 sun.tools.jcmd.JCmd
居然還在,唯一的借口都不給我贺待!
檢查Java進(jìn)程棧
我第一個(gè)想到的是死鎖徽曲,導(dǎo)致進(jìn)程假死◆锶看看Java棧:
[web@monitor ~]$ jstack -l 19335
2017-11-03 21:44:07
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.144-b01 mixed mode):
"http-nio-8087-exec-10" #1017 daemon prio=5 os_prio=0 tid=0x00007f2cd0016000 nid=0x5c31 waiting on condition [0x00007f2c9d21a000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000000920abcf8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:103)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:31)
at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)
Locked ownable synchronizers:
- None
.....
太長(zhǎng)了,此處截取部分涧衙。我記得jstack 有時(shí)候可以幫我們檢測(cè)出死鎖的哪工,可以直接
jstack -l <pid> | grep deadlock
如果有結(jié)果,則表示有死鎖弧哎,當(dāng)然也不排除其他情況(ps: 也遇到過數(shù)據(jù)庫(kù)鏈接失效雁比,導(dǎo)致某任務(wù)執(zhí)行了3個(gè)多小時(shí)后失敗的。)jstack的其他情況可以參考下:http://blog.csdn.net/wanglha/article/details/51133819
檢查Java堆
Java對(duì)象大部分情況實(shí)在堆上創(chuàng)建的(有時(shí)候會(huì)在棧上或者有塊叫做TLAB的空間)撤嫩,那么來檢查下堆的情況吧偎捎。
[web@monitor ~]$ jmap -heap 19335
Attaching to process ID 19335, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.144-b01
using thread-local object allocation.
Parallel GC with 2 thread(s)
Heap Configuration:
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
MaxHeapSize = 1879048192 (1792.0MB)
NewSize = 625999872 (597.0MB)
MaxNewSize = 625999872 (597.0MB)
OldSize = 1253048320 (1195.0MB)
NewRatio = 2
SurvivorRatio = 5
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)
Heap Usage:
PS Young Generation
Eden Space:
capacity = 569901056 (543.5MB)
used = 105560864 (543.5MB)
free = 464340192 (0MB)
100% used
From Space:
capacity = 27262976 (26.0MB)
used = 6621032 (6.314308166503906MB)
free = 20641944 (19.685691833496094MB)
24.28580064039964% used
To Space:
capacity = 26214400 (25.0MB)
used = 0 (0.0MB)
free = 26214400 (25.0MB)
0.0% used
PS Old Generation
capacity = 1253048320 (1195.0MB)
used = 55013328 (1195.0MB)
free = 1198034992 (0MB)
100% used
納尼!P蛉痢茴她!Eden區(qū)和老年代都耗盡了(這些數(shù)據(jù)是后來假造的,當(dāng)時(shí)的確看到的是兩個(gè)100%)程奠。
看看gc情況
[web@monitor ~]$ jstat -gcutil 19335
S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
0.00 99.79 99.89 98.99 97.96 96.07 901 3.654 287 1.160 4.814
介紹下這幾個(gè)參數(shù)丈牢,S0、S1表示兩個(gè)Survivor區(qū)(復(fù)制GC算法使用)瞄沙,E表示Eden區(qū)己沛,大部分對(duì)象在此創(chuàng)建,O老年代距境,多次GC后對(duì)象存活區(qū)域申尼,M表示元數(shù)據(jù)區(qū)(JAVA8后取代永久代,存放一些class元數(shù)據(jù)垫桂,我們不用太關(guān)心它师幕,基本上有JVM幫我們管理),YGC和FGC分別表示Eden區(qū)GC次數(shù)和Full GC的次數(shù)伪货,正常情況下Full GC次數(shù)比較少们衙,跑個(gè)幾天Full GC幾次差不多了,如果很多次就不正常了(當(dāng)然排除哪個(gè)二貨手動(dòng)調(diào)用
System.gc()
)碱呼,F(xiàn)ull GC比較多蒙挑,你可能會(huì)看到如下錯(cuò)誤:java.lang.OutOfMemoryError: GC overhead limit exceeded
,這個(gè)表示JVM試圖通過GC回收內(nèi)存愚臀,但是什么也沒有回收到忆蚀。默認(rèn)情況下,JVM花費(fèi)了98%的時(shí)間在GC上,但是GC過之后只有不到2%的堆內(nèi)存被回收馋袜。
甩鍋記之內(nèi)存的鍋
我去看下這群貨到底要下載什么數(shù)據(jù)男旗,一看嚇一跳,兩個(gè)月全國(guó)所有的保單數(shù)據(jù)欣鳖。這是什么概念呢察皇??jī)蓚€(gè)月差不多20多萬數(shù)據(jù)吧,如果不做關(guān)聯(lián)單泽台,那差不多是30多萬到40萬左右什荣,30萬行的Excel什么概念?自己想想吧怀酷。
我當(dāng)然不能說你們別下這么多數(shù)據(jù)啊稻爬,反正我一個(gè)打工小弟說了也沒人聽啊蜕依!好吧桅锄,升級(jí)內(nèi)存總可以吧,這鍋你背样眠。
java -Xms1536m -Xmx1536m -jar service.jar
初始化內(nèi)存和最大內(nèi)存設(shè)置一樣是為了減少GC次數(shù)友瘤,為啥要減少GC呢?簡(jiǎn)單說就是GC比較霸道吹缔,它工作的時(shí)候
STOP THE WORLD
商佑,所有的工作都得停下,等待GC完成厢塘。話說什么時(shí)候開始GC呢茶没?你可以使用如下命令觀察下:
jstat -gcutil <pid> [intervalTime] [invokeCount]
其中intervalTime表示多久執(zhí)行一次(單位毫秒),invokeCount表示總共會(huì)執(zhí)行多少次晚碾。如:jstat -gcutil 19335 1000
大概當(dāng)Eden區(qū)打到100%時(shí)會(huì)發(fā)生一次Young GC抓半,F(xiàn)ull GC類似。
堆內(nèi)存的各個(gè)區(qū)
改完內(nèi)存繼續(xù)觀察(后面還會(huì)升內(nèi)存格嘁,畢竟這鍋給內(nèi)存背比較簡(jiǎn)單??)笛求,發(fā)現(xiàn)其實(shí)Eden、Survivor糕簿、Old區(qū)使用情況有很大差異探入,有的區(qū)很快就滿了,有的還不到才20幾懂诗。不行啊蜂嗽,得改,平均點(diǎn)Q旰恪(這里說的是使用情況植旧,不是都一樣大)辱揭。
各個(gè)區(qū)大小比例
可以通過jmap命令查看
[jarvan4dev@Macbook] ~ $ jmap -heap 34890
Attaching to process ID 34890, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.74-b02
using thread-local object allocation.
Parallel GC with 4 thread(s)
Heap Configuration:
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
MaxHeapSize = 2147483648 (2048.0MB)
NewSize = 44564480 (42.5MB)
MaxNewSize = 715653120 (682.5MB)
OldSize = 89653248 (85.5MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)
默認(rèn)情況下(JDK8,不同版本可能不一樣)病附,NewRatio=2问窃,表示新生代和老年代比例為1:2,SurvivorRatio=8完沪,表示一個(gè)Survivor區(qū)和新生代比例為1:8域庇,兩個(gè)Survivor區(qū)(S0和S1),即每個(gè)Survivor區(qū)占堆內(nèi)存的1/10丽焊,調(diào)節(jié)這兩個(gè)參數(shù)可以調(diào)節(jié)各區(qū)大小比例较剃,來達(dá)到最優(yōu)的使用情況。
java -Xms1792m -XmX1792m -XX:NewRatio=2 -XX:SurvivorRatio=5 -jar service.jar