本文介紹JVM監控統計的記憶體指標說明。
Java進程佔用記憶體分布
Java進程在運行期間,記憶體分布的大致情況如下圖所示:
JVM的運行機制比較複雜,此圖僅列出了主要的記憶體分布地區。
ARMS擷取JVM記憶體詳情的原理
ARMS應用監控探針通過JDK提供的MemoryMXBean擷取JVM運行期間記憶體詳情,受限於MemoryMXBean的運行機制,目前ARMS的JVM記憶體監控能力還不能覆蓋Java進程佔用的所有記憶體地區。更多詳情請參見Java官方網站的MemoryMXBean介紹。
堆記憶體詳情
堆(Heap)部分是Java記憶體中的核心部分,所有對象都在這裡分配記憶體,是垃圾收集的主要區域。根據Java垃圾收集器的不同,ARMS展示的最大允許使用堆記憶體有可能會略小於使用者佈建的堆記憶體上限。例如在ParallelGC垃圾收集器中,設定-XX:+UseParallelGC -Xms4096m -Xmx4096m
參數,有可能會產生下圖的結果,其中最大允許使用記憶體為3.8 G,略小於使用者佈建的4 G。這是因為在這種情況下MemoryMXBean收集的資料並沒有包含From Space和To Space地區,導致了細微的偏差。
一般情況下,如果使用G1垃圾收集器,ARMS展示的最大允許使用堆記憶體和使用者佈建的-Xmx
或-XX:MaxRAMPercentage
保持一致,而在使用Parallel、ConcMarkSweepGC和Serial垃圾收集器的情況下,存在細微的偏差。
元空間詳情
元空間(Meta Space)用於存放類的中繼資料,包括類的結構資訊、方法資訊、欄位資訊等,這一部分的記憶體佔用一般比較穩定。
非堆記憶體詳情
ARMS展示的非堆記憶體包括元空間(Meta Space)、壓縮類空間(Compressed Class Space)和代碼緩衝區(Code Cache)三個地區的總和。受限於MemoryMXBean的工作原理,ARMS展示的非堆記憶體並不直接等同於Heap之外的部分,虛擬機器線程棧(VM Thread Stacks)以及JNI本地代碼等地區都不包含在ARMS展示的非堆記憶體中。
元空間(Meta Space):元空間儲存類的元資訊。JDK 8後,元空間的預設大小和最大大小一般可以通過
-XX:MetaspaceSize=N
和-XX:MaxMetaspaceSize=N
來指定。壓縮類空間(Compressed Class Space):壓縮類空間是Java虛擬機器的一個特殊地區,用於壓縮和儲存已載入的類別中繼資料。它通過減小指標的儲存空間來降低記憶體佔用,從而減少Java應用程式的記憶體消耗。壓縮類空間可以通過JVM啟動參數
-XX:CompressedClassSpaceSize
來指定,在JDK 11中,預設的壓縮類空間大小為1 GB。代碼緩衝區(Code Cache):JVM自身會產生一些Native Code並將其儲存在稱為Code Cache的記憶體地區中。JVM產生Native Code的原因有很多,包括動態產生的解譯器迴圈、JNI、即時編譯器(JIT)編譯Java方法產生的機器碼。其中JIT產生的Native Code佔據了Code Cache絕大部分的空間。代碼緩衝區可以通過JVM啟動參數
-XX:InitialCodeCacheSize
和-XX:ReservedCodeCacheSize
分別配置其起始大小與最大大小。
直接緩衝區
Java中的直接緩衝區(Direct Buffer)是一種特殊類型的緩衝區,它直接在作業系統的記憶體中分配空間,而不是在Java虛擬機器的堆記憶體中分配。直接緩衝區的主要特點是可以提供更快的I/O操作,並且可以避免記憶體複製的開銷,因此在處理大量資料時非常高效。大量的I/O操作會增加直接緩衝區記憶體佔用。
堆記憶體泄露分析
ARMS提供了完善的堆記憶體泄露定位分析能力,使用者可以通過JVM堆記憶體監控查看是否出現堆記憶體緩慢增長,如果存在相關堆記憶體持續增長趨勢,可以通過ARMS提供的記憶體快照或持續剖析功能排查定位堆內記憶體泄露位置。
堆外記憶體泄露分析
如果堆記憶體比較穩定,但應用總體記憶體在一直增長,未有下降跡象,有可能是堆外記憶體泄露引起。ARMS目前不提供堆外記憶體分析能力,可以考慮使用Native Memory Tracking(NMT)工具對堆外記憶體申請情況進行監測,具體使用說明請參見官方文檔。
NMT工具存在一定的使用門檻 ,而且會對應用產生5~10%的效能開銷,線上應用使用請先評估影響後再進行。
記憶體常見問題
為什麼ARMS應用監控產品介面上看到的堆、非堆記憶體總和與通過
top
命令看到的RES相差很多?答:ARMS應用監控採集的資料來源來自JMX,並不包含虛擬機器線程棧、本地線程棧等部分,以及非JVM記憶體部分。所以ARMS應用監控展示的JVM記憶體資訊並不等同於通過
top
命令看到的RES。為什麼ARMS應用監控產品介面上看到的堆、非堆記憶體總和與在Prometheus、Grafana中看到的記憶體使用量資料相差很多?
答:ARMS應用監控採集的資料來源來自JMX,而Grafana上看到的記憶體使用量率是通過Prometheus Query Language查詢的指標,一般取與對應用程式容器相關的、名字包含container_memory_working_set_bytes的指標,實際上統計的是memcg(進程組)的rss以及active cache的總和。所以兩者存在差異。
如果發現有Pod由於OOM Killer導致重啟,如何通過ARMS應用監控排查?
答:ARMS應用監控對於堆記憶體、直接緩衝區的容量規劃問題,比較容易排查。但由於ARMS應用監控是從JMX擷取記憶體資料,無法覆蓋整個JVM進程的RSS消耗,因此OOM Killer問題需要藉助K8s的Prometheus監控生態來排查。另外,特別需要注意以下兩個方面:
Pod內是單進程模型嗎?
用於排除其他進程記憶體消耗的幹擾。
JVM進程外是否存在泄漏?
例如glibc導致的記憶體流失。