聊聊Java中的內存

JVM的內存

先放一張JVM的內存劃分圖,總體上可以分爲堆和非堆(粗略劃分,基於java8)
jvm memory

那麼一個Java進程最大佔用的物理內存爲:

Max Memory = eden + survivor + old + String Constant Pool + Code cache + compressed class space + Metaspace + Thread stack(*thread num) + Direct + Mapped + JVM + Native Memory

堆和非堆內存

堆和非堆內存有以下幾個概念:

init

表示JVM在啓動時從操作系統申請內存管理的初始內存大小(以字節爲單位)。JVM可能從操作系統請求額外的內存,也可以隨着時間的推移向操作系統釋放內存(經實際測試,這個內存並沒有過主動釋放)。這個init的值可能不會定義。

used

表示當前使用的內存量(以字節爲單位)

committed

表示保證可供 Jvm使用的內存大小(以字節爲單位)。 已提交內存的大小可能隨時間而變化(增加或減少)。 JVM也可能向系統釋放內存,導致已提交的內存可能小於 init,但是committed永遠會大於等於used。

max

表示可用於內存管理的最大內存(以字節爲單位)。

NMT追蹤內存

NMT(Native Memory tracking)是一種Java HotSpot VM功能,可跟蹤Java HotSpot VM的內部內存使用情況(jdk8+)。

本文簡單介紹下該工具的使用,主要用來解釋Java中的內存

開啓

在啓動參數中添加-XX:NativeMemoryTracking=detail

查看

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

reserved memory 是指JVM 通過mmaped PROT_NONE 申請的虛擬地址空間,在頁表中已經存在了記錄(entries),保證了其他進程不會被佔用。
在堆內存下,就是xmx值,jvm申請的最大保留內存。

committed

committed memory 是JVM向操做系統實際分配的內存(malloc/mmap),mmaped PROT_READ | PROT_WRITE,相當於程序實際申請的可用內存。

在堆內存下,就是xms值,最小堆內存,heap committed memory。

注意,committed申請的內存並不是說直接佔用了物理內存,由於操作系統的內存管理是惰性的,對於已申請的內存雖然會分配地址空間,但並不會直接佔用物理內存,真正使用的時候纔會映射到實際的物理內存。所以committed > rss/res也是很可能的

Linux內存與JVM內存

再來說說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/RSS的大小。

所以可能會出現進程所用內存大於當前堆+非堆的情況。

比如說該Java程序在5分鐘前,有一定活動,佔用了2.6G堆內存(無論堆中的什麼代),經過GC之後,雖然堆內存已經被回收了,堆佔用很低,但GC的回收只是針對Jvm申請的這塊內存區域,並不會調用操作系統釋放內存。所以該進程的內存並不會釋放,這時就會出現進程內存遠遠大於堆+非堆的情況。

至於Oracle文檔上說的,Jvm可能會向操作系統釋放內存,經過測試沒有發現釋放的情況。不過就算有主動釋放的情況,也不太需要我們程序關心了。

如果拋開Native Memory不看的話,堆最大內存(Xmx)+ 非堆內存總是會小進程所用內存的

RES/RSS(Resident Set Size)是常駐內存的意思,進程實際使用的物理內存

參考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章