先放一張JVM的內存劃分圖,整體上能夠分爲堆和非堆(粗略劃分,基於java8)
html
那麼一個Java進程最大佔用的物理內存爲:java
Max Memory = eden + survivor + old + String Constant Pool + Code cache + compressed class space + Metaspace + Thread stack(*thread num) + Direct + Mapped + JVM + Native Memory
堆和非堆內存有如下幾個概念:緩存
表示JVM在啓動時從操做系統申請內存管理的初始內存大小(以字節爲單位)。JVM可能從操做系統請求額外的內存,也能夠隨着時間的推移向操做系統釋放內存(經實際測試,這個內存並無過主動釋放)。這個init的值可能不會定義。oracle
表示當前使用的內存量(以字節爲單位)app
表示保證可供 Jvm使用的內存大小(以字節爲單位)。 已提交內存的大小可能隨時間而變化(增長或減小)。 JVM也可能向系統釋放內存,致使已提交的內存可能小於 init,可是committed永遠會大於等於used。jvm
表示可用於內存管理的最大內存(以字節爲單位)。ide
NMT(Native Memory tracking)是一種Java HotSpot VM功能,可跟蹤Java HotSpot VM的內部內存使用狀況(jdk8+)。工具
本文簡單介紹下該工具的使用,主要用來解釋Java中的內存測試
在啓動參數中添加-XX:NativeMemoryTracking=detail
ui
jcmd 進程id VM.native_memory summary scale=MB
Native Memory Tracking: Total: reserved=6988749KB, committed=3692013KB 堆內存 - Java Heap (reserved=5242880KB, committed=3205008KB) (mmap: reserved=5242880KB, committed=3205008KB) 類加載信息 - Class (reserved=1114618KB, committed=74642KB) (classes #10657) (malloc=4602KB #32974) (mmap: reserved=1110016KB, committed=70040KB) 線程棧 - Thread (reserved=255213KB, committed=255213KB) (thread #248) (stack: reserved=253916KB, committed=253916KB) (malloc=816KB #1242) (arena=481KB #494) 代碼緩存 - Code (reserved=257475KB, committed=46551KB) (malloc=7875KB #10417) (mmap: reserved=249600KB, committed=38676KB) 垃圾回收 - GC (reserved=31524KB, committed=23560KB) (malloc=17180KB #2113) (mmap: reserved=14344KB, committed=6380KB) 編譯器 - Compiler (reserved=598KB, committed=598KB) (malloc=467KB #1305) (arena=131KB #3) 內部 - Internal (reserved=6142KB, committed=6142KB) (malloc=6110KB #23691) (mmap: reserved=32KB, committed=32KB) 符號 - Symbol (reserved=11269KB, committed=11269KB) (malloc=8544KB #89873) (arena=2725KB #1) nmt - Native Memory Tracking (reserved=2781KB, committed=2781KB) (malloc=199KB #3036) (tracking overhead=2582KB) - Arena Chunk (reserved=194KB, committed=194KB) (malloc=194KB) - Unknown (reserved=66056KB, committed=66056KB) (mmap: reserved=66056KB, committed=66056KB)
nmt返回結果中有reserved和committed兩個值,這裏解釋一下:
reserved memory 是指JVM 經過mmaped PROT_NONE 申請的虛擬地址空間,在頁表中已經存在了記錄(entries),保證了其餘進程不會被佔用。
在堆內存下,就是xmx值,jvm申請的最大保留內存。
committed memory 是JVM向操作系統實際分配的內存(malloc/mmap),mmaped PROT_READ | PROT_WRITE,至關於程序實際申請的可用內存。
在堆內存下,就是xms值,最小堆內存,heap committed memory。
注意,committed申請的內存並非說直接佔用了物理內存,因爲操做系統的內存管理是惰性的,對於已申請的內存雖然會分配地址空間,但並不會直接佔用物理內存,真正使用的時候纔會映射到實際的物理內存。因此committed > res也是極可能的
再來講說JVM內存與該進程的內存。
如今有一個Java進程,JVM全部已使用內存區域加起來才2G(不包括Native Memory,也沒有顯式調用JNI的地方),但從top/pmap上看該進程res已經2.9G了
#heap + noheap Memory used total max usage heap 1921M 2822M 4812M 39.93% par_eden_space 1879M 2457M 2457M 76.47% par_survivor_space 4M 307M 307M 1.56% cms_old_gen 37M 57M 2048M 1.84% nonheap 103M 121M -1 85.00% code_cache 31M 37M 240M 13.18% metaspace 63M 74M -1 85.51% compressed_class_space 7M 9M 1024M 0.75% direct 997K 997K - 100.00 mapped 0K 0K - NaN%
#top top -p 6267 top - 17:39:40 up 140 days, 5:39, 5 users, load average: 0.00, 0.01, 0.00 Tasks: 1 total, 0 running, 1 sleeping, 0 stopped, 0 zombie Cpu(s): 0.2%us, 0.1%sy, 0.0%ni, 99.7%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st Mem: 8059152k total, 5255384k used, 2803768k free, 148872k buffers Swap: 0k total, 0k used, 0k free, 1151812k cached PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 6267 root 20 0 8930m 2.9g 17m S 0.0 37.6 4:13.31 java
那麼其他的0.9G內存去哪了呢?
這時候就要介紹下JVM與Linux內存的聯繫了
當Java程序啓動後,會根據Xmx爲堆預申請一塊保留內存,並不會直接使用,也不會佔用物理內存
而後申請(malloc之類的方法)Xms大小的虛擬內存,可是因爲操做系統的內存管理是惰性的,有一個內存延遲分配的概念。malloc雖然會分配內存地址空間,可是並無映射到實際的物理內存,只有當對該地址空間賦值時,纔會真正的佔用物理內存,纔會影響RES的大小。
因此可能會出現進程所用內存大於當前堆+非堆的狀況。
好比說該Java程序在5分鐘前,有必定活動,佔用了2.6G堆內存(不管堆中的什麼代),通過GC以後,雖然堆內存已經被回收了,堆佔用很低,但GC的回收只是針對Jvm申請的這塊內存區域,並不會調用操做系統釋放內存。因此該進程的內存並不會釋放,這時就會出現進程內存遠遠大於堆+非堆的狀況。
至於Oracle文檔上說的,Jvm可能會向操做系統釋放內存,通過測試沒有發現釋放的狀況。不過就算有主動釋放的狀況,也不太須要咱們程序關心了。
RES(Resident Set Size)是常駐內存的意思,進程實際使用的物理內存