解决JVM堆内存不断扩容导致服务器内存耗尽的问题
文章目錄
- 應用場景:
- 問題描述:
- 原因分析:
- 解決方案:
- 軟件版本
應用場景:
采用Spring Boot搭建Web應用,打成jar包,通過內(nèi)置Tomcat運行。每臺服務器上面部署了十幾個應用,都是通過java -jar xxx.jar啟動,整個系統(tǒng)可以正常運行。
問題描述:
在系統(tǒng)運行一段時間后,發(fā)現(xiàn)服務器內(nèi)存被占滿。
[root@i ~]# free -htotal used free shared buff/cache available Mem: 125G 112G 3.0G 439M 10G 11G Swap: 14G 6.2G 8.4G從上面的內(nèi)存信息中,可以看出可用的內(nèi)存空間已經(jīng)不足。然后通過top命令查看進程占用內(nèi)存的情況
使進程按內(nèi)存占用大小倒序排列,圖中顯示占用內(nèi)存多的都是java程序。
看到這些信息,初步判定是發(fā)生內(nèi)存泄漏了,對象無法回收,導致堆內(nèi)存老年代不斷增加。接下去我們選擇一個占用內(nèi)存最大的進程來分析,驗證當前的判斷。
原因分析:
通過jps -l可以列出JVM進程號和應用名稱,如下圖所示
對比進程號就可以看出哪個程序占用內(nèi)存多。然后使用jstat命令查看Java進程中JVM內(nèi)存的使用情況
[root@i ~]# jstat -gcutil 2113 1000 1S0 S1 E O M CCS YGC YGCT FGC FGCT GCT 0.00 99.85 12.80 30.18 93.81 90.63 1975 125.783 5 1.049 126.832這里顯示的是JVM各個內(nèi)存區(qū)域的使用占比。除了占比我們還可以看一下實際占用內(nèi)存的大小
[root@i ~]# jstat -gc 2113 1000 1S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT 191488.0 52736.0 0.0 52656.0 10113024.0 1490370.4 2833920.0 855148.1 118292.0 110974.1 13884.0 12582.7 1975 125.783 5 1.049 126.832這里顯示了各個內(nèi)存區(qū)域的大小和已使用的大小,單位是KB。當然這種方式可讀性比較差,不直觀,為了方便的查看JVM運行信息呢,
我選擇使用VisualVM通過遠程連接查看服務器上面Java進程。
這里顯示了服務器上面2113這個進程的JVM內(nèi)存信息,可以看出堆內(nèi)存大小為12.7G,實際使用為2.9G,實際占用并不多。
接下來我們通過Visual GC插件來看一下堆內(nèi)存年輕代和老年代的分布情況
從這張圖上面,我們可以直觀的看出實際內(nèi)存使用并不多,大量的內(nèi)存處于空閑狀態(tài),到這里我們可以確定程序并沒有出現(xiàn)內(nèi)存泄漏,對象可以被正常回收。
但是大量的內(nèi)存空閑,占用了系統(tǒng)的本地內(nèi)存,影響了其他程序的運行。其中Eden區(qū)最大,占了9.6G,最大值是9.9G。這個最大值9.9G是一個默認值,
如果啟動Java程序的時候設置JVM參數(shù),則使用默認值。我們可以使用jinfo命令看一下Java進程啟動時設置的參數(shù)
從上面的信息中可以得出最大堆內(nèi)存MaxHeapSize=32210157568(29.9G),最大新生代內(nèi)存MaxNewSize=10736369664(9.9G),
MinHeapDeltaBytes=524288這是堆內(nèi)存最小擴容值512KB,當Eden區(qū)域不夠時,JVM會嘗試擴容來避免頻繁的垃圾回收。
對于并發(fā)量高的程序,在短時間內(nèi)創(chuàng)建大量新對象,Eden區(qū)域不足,觸發(fā)擴容,直到最大值,在垃圾回收之后又空出大量的內(nèi)存。
因為每個程序的并發(fā)量不一樣,所以擴容的速度也不一樣。沒有設置JVM參數(shù)是導致JVM內(nèi)存不斷擴容,占用大量內(nèi)存的最大原因。
解決方案:
根據(jù)以上分析結(jié)果,針對JVM內(nèi)存的實際使用情況,在程序運行時設置JVM參數(shù)
java -server -Xms3g -Xmx3g -Xmn2g -XX:SurvivorRatio=8 -XX:-UseAdaptiveSizePolicy -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m -XX:+UseParallelGC -XX:+UseParallelOldGC -jar xxx.jar采用server模式可以優(yōu)化預編譯處理,提高程序后續(xù)的執(zhí)行速度。
堆內(nèi)存大小設置為3G,可以根據(jù)程序的實際負載調(diào)整堆內(nèi)存大小,一般程序3個G夠用了。
年輕代大小設置為2G,Survivor區(qū)和Eden區(qū)比值設置為1:1:8,增加年輕代大小可以避免頻繁垃圾回收,導致對象提前進入老年代。
因為并發(fā)垃圾回收器默認使用了-XX:+UseAdaptiveSizePolicy,所以即使設置了SurvivorRatio,Survivor區(qū)和Eden區(qū)的大小還是自適應的,這會導致Survivor區(qū)很小,在垃圾回收時如果Survivor區(qū)裝不下,對象會直接進入老年代,所以我們使用-XX:-UseAdaptiveSizePolicy關閉內(nèi)存大小自適應。
元空間觸發(fā)垃圾回收的值設置為128M,MetaspaceSize不是元空間的初始大小,而首次觸發(fā)垃圾回收的閾值,設置一個足夠大的值可以避免程序加載過程中觸發(fā)垃圾回收,降低程序運行效率。
年輕代和老年代都使用并發(fā)垃圾回收器,減少垃圾回收時程序的等待時間。
通過對JVM參數(shù)的優(yōu)化,即避免Java進程過多的占用內(nèi)存,又可以提高程序的運行效率。
軟件版本
JDK:1.8
Spring Boot:2.1.9.RELEASE
總結(jié)
以上是生活随笔為你收集整理的解决JVM堆内存不断扩容导致服务器内存耗尽的问题的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Service注入不进去
- 下一篇: 乡村改造之色彩运用