Java內(nèi)存優(yōu)化之POI Excel(一)

結(jié)算系統(tǒng)上線后,每到月初月末,都有點(diǎn)膽戰(zhàn)心驚,最怕聽到“某某某,我這個下載又不行”、“我這個都下載了20分鐘了,怎么還不行?。 ?..... 我能怎么辦哇,停下來把鍋撿起來唄。

撿鍋記之檢鍋

撿鍋了,然后呢?當(dāng)然是查一查問題出在哪了。ssh上服務(wù)器,先說說服務(wù)器配置吧。這臺服務(wù)器是在某離職大神的建議下購買的,配置還不錯。單核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)剛升級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)程棧

我第一個想到的是死鎖,導(dǎo)致進(jìn)程假死??纯碕ava棧:

[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
..... 

太長了,此處截取部分。我記得jstack 有時(shí)候可以幫我們檢測出死鎖的,可以直接

jstack -l <pid> | grep deadlock

如果有結(jié)果,則表示有死鎖,當(dāng)然也不排除其他情況(ps: 也遇到過數(shù)據(jù)庫鏈接失效,導(dǎo)致某任務(wù)執(zhí)行了3個多小時(shí)后失敗的。)jstack的其他情況可以參考下:http://blog.csdn.net/wanglha/article/details/51133819

檢查Java堆

Java對象大部分情況實(shí)在堆上創(chuàng)建的(有時(shí)候會在棧上或者有塊叫做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

納尼?。?!Eden區(qū)和老年代都耗盡了(這些數(shù)據(jù)是后來假造的,當(dāng)時(shí)的確看到的是兩個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

介紹下這幾個參數(shù),S0、S1表示兩個Survivor區(qū)(復(fù)制GC算法使用),E表示Eden區(qū),大部分對象在此創(chuàng)建,O老年代,多次GC后對象存活區(qū)域,M表示元數(shù)據(jù)區(qū)(JAVA8后取代永久代,存放一些class元數(shù)據(jù),我們不用太關(guān)心它,基本上有JVM幫我們管理),YGC和FGC分別表示Eden區(qū)GC次數(shù)和Full GC的次數(shù),正常情況下Full GC次數(shù)比較少,跑個幾天Full GC幾次差不多了,如果很多次就不正常了(當(dāng)然排除哪個二貨手動調(diào)用System.gc()),F(xiàn)ull GC比較多,你可能會看到如下錯誤:java.lang.OutOfMemoryError: GC overhead limit exceeded,這個表示JVM試圖通過GC回收內(nèi)存,但是什么也沒有回收到。默認(rèn)情況下,JVM花費(fèi)了98%的時(shí)間在GC上,但是GC過之后只有不到2%的堆內(nèi)存被回收。

甩鍋記之內(nèi)存的鍋

我去看下這群貨到底要下載什么數(shù)據(jù),一看嚇一跳,兩個月全國所有的保單數(shù)據(jù)。這是什么概念呢?兩個月差不多20多萬數(shù)據(jù)吧,如果不做關(guān)聯(lián)單,那差不多是30多萬到40萬左右,30萬行的Excel什么概念?自己想想吧。

我當(dāng)然不能說你們別下這么多數(shù)據(jù)啊,反正我一個打工小弟說了也沒人聽??!好吧,升級內(nèi)存總可以吧,這鍋你背。

java -Xms1536m -Xmx1536m -jar service.jar

初始化內(nèi)存和最大內(nèi)存設(shè)置一樣是為了減少GC次數(shù),為啥要減少GC呢?簡單說就是GC比較霸道,它工作的時(shí)候STOP THE WORLD,所有的工作都得停下,等待GC完成。話說什么時(shí)候開始GC呢?你可以使用如下命令觀察下:

jstat -gcutil <pid> [intervalTime] [invokeCount]
其中intervalTime表示多久執(zhí)行一次(單位毫秒),invokeCount表示總共會執(zhí)行多少次。如:jstat -gcutil 19335 1000

大概當(dāng)Eden區(qū)打到100%時(shí)會發(fā)生一次Young GC,F(xiàn)ull GC類似。

堆內(nèi)存的各個區(qū)

改完內(nèi)存繼續(xù)觀察(后面還會升內(nèi)存,畢竟這鍋給內(nèi)存背比較簡單??),發(fā)現(xiàn)其實(shí)Eden、Survivor、Old區(qū)使用情況有很大差異,有的區(qū)很快就滿了,有的還不到才20幾。不行啊,得改,平均點(diǎn)?。ㄟ@里說的是使用情況,不是都一樣大)。

各個區(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,表示一個Survivor區(qū)和新生代比例為1:8,兩個Survivor區(qū)(S0和S1),即每個Survivor區(qū)占堆內(nèi)存的1/10,調(diào)節(jié)這兩個參數(shù)可以調(diào)節(jié)各區(qū)大小比例,來達(dá)到最優(yōu)的使用情況。

java -Xms1792m -XmX1792m -XX:NewRatio=2 -XX:SurvivorRatio=5 -jar service.jar
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容