爲什麼JVM佔用了超過-Xmx配置的內存?
持續創作,加速成長!這是我參與「掘金日新計劃 · 6 月更文挑戰」的第9天,點擊查看活動詳情
前言
一般JVM我們都會配置上 Xmx參數,限制堆內存的使用
但是在top指令或者其他的指令上看到的值卻不是我們期望的Xmx的數字
實際上Xmx佔用的內存只是JVM所佔內存的一部分,JVM經過各種版本的演化,實際上有了許多的變化
可以從下圖看出
一個比較大的變化是 JDK8
已經沒有了PermSpace, 增加了 metaspace 的區域,用來保存常量池和類常量池,這部分的內存不是分配在堆上的,而是分配在堆外的,通常如果不設置的話,這部分會無限增長,使用 jmap -heap 指令看出maxmetaspacesize的值非常大,可以認爲就是沒有限制。
這部分的堆外內存只有在full gc時纔會被回收
metaspace
可以通過如下幾個參數對metaspace 的大小進行限制
- -XX:MetaspaceSize=N :這個參數是初始化的Metaspace大小,該值越大觸發Metaspace GC的時機就越晚。隨着GC的到來,虛擬機會根據實際情況調控Metaspace的大小,可能增加上線也可能降低。在默認情況下,這個值大小根據不同的平臺在12M到20M浮動。使用java -XX:+PrintFlagsInitial命令查看本機的初始化參數,-XX:Metaspacesize爲21810376B(大約20.8M)
- -XX:MaxMetaspaceSize=N :這個參數用於限制Metaspace增長的上限,防止因爲某些情況導致Metaspace無限的使用本地內存,影響到其他程序。默認無上限。
- -XX:MinMetaspaceFreeRatio=N :當進行過Metaspace GC之後,會計算當前Metaspace的空閒空間比,如果空閒比小於這個參數,那麼虛擬機將增長Metaspace的大小。在本機該參數的默認值爲40,也就是40%。設置該參數可以控制Metaspace的增長的速度,太小的值會導致Metaspace增長的緩慢,Metaspace的使用逐漸趨於飽和,可能會影響之後類的加載。而太大的值會導致Metaspace增長的過快,浪費內存。
- -XX:MaxMetasaceFreeRatio=N :當進行過Metaspace GC之後, 會計算當前Metaspace的空閒空間比,如果空閒比大於這個參數,那麼虛擬機會釋放Metaspace的部分空間。在本機該參數的默認值爲70,也就是70%。
- -XX:MaxMetaspaceExpansion=N :Metaspace增長時的最大幅度。在本機上該參數的默認值爲5452592B(大約爲5MB)
- -XX:MinMetaspaceExpansion=N :Metaspace增長時的最小幅度。在本機上該參數的默認值爲340784B(大約330KB爲)
一個誤解
使用jstat -gcutil的結果中的M代表的是元空間的使用比例。又例如:測試環境上某個服務爲例,配置了-XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=256m
,通過jstat -gcutil pid
查看M
的值爲98.32
,即Meta區使用率也達到了98.32%
:
然後,再通過jstat -gc 4210 2s 3
命名查看,結果如下圖所示,計算MU/MC即Meta區的使用率確實達到了98.32%
,但是MC,即Metaspace Capacity只有55296k,並不是參數MetaspaceSize
指定的256m:
那麼-XX:MetaspaceSize=256m
的含義到底是什麼呢?
其實,這個JVM參數是指Metaspace擴容時觸發FullGC的初始化閾值,也是最小的閾值。這裏有幾個要點需要明確:
- 如果沒有配置
-XX:MetaspaceSize
,那麼觸發FGC的閾值是21807104(約20.8m),可以通過jinfo -flag MetaspaceSize pid得到這個值;jps -v也可以查看jvm的參數設置情況。 - 如果配置了
-XX:MetaspaceSize
,那麼觸發FGC的閾值就是配置的值; - Metaspace由於使用不斷擴容到
-XX:MetaspaceSize
參數指定的量,就會發生FGC;且之後每次Metaspace擴容都可能會發生FGC(至於什麼時候會,比較複雜,跟幾個參數有關); - 如果Old區配置CMS垃圾回收,那麼擴容引起的FGC也會使用CMS算法進行回收;
- 如果MaxMetaspaceSize設置太小,可能會導致頻繁FullGC,甚至OOM;
建議
MetaspaceSize
和MaxMetaspaceSize
設置一樣大;- 具體設置多大,建議穩定運行一段時間後通過
jstat -gc pid
確認且這個值大一些,對於大部分項目256m即可。目前我們的使用也是這一個值
\
實際的JVM內存佔用如何計算
Max memory = [-Xmx] + [-XX:MaxPermSize] + number_of_threads * [-Xss]
所以實際如果我們配置瞭如下參數
-Xmx512m -Xss1m -MaxMetaSpaceSize=256m 的JVM實際佔用的大小爲 512+(1*線程數)+256
可以通過如下指令輸出JVM 的內存佔用,使用這個指令需要在JVM啓動時添加參數
java -XX:NativeMemoryTracking=detail
jcmd pid VM.native_memory
輸出如下,可以看到整個JVM使用的內存就是由一下的這些部分組成的
總結
當時的服務實際上時佔用了2.5G的內存,這部分因爲當時沒有加上跟蹤參數,所以暫時還無法查看到
根據以上信息推論可以推論得到,佔用的大頭應該是metaspace
這部分的內存一開始並沒有限制,不會因爲這部分的內容觸發 full gc,動態產生的class都會放在這個區域,進程運行的時間越長,這個區域就會越大,同時也會觀察到實際的堆佔用並沒有增加
現在開發環境採用的參數爲 -Xmx512m -Xss512k -XX:MaxMetaspaceSize
=256m,需要觀察一段時間看是否限制住了
也可以通過命令 jcmd pid VM.native_memory 建立的快照來對比發生泄漏的區域,找到問題的根源