如果想觀察JVM進程占用的堆內存,可以通過命令工具jmap或者可視化工具jvisualvm.exe。JVM這些啟動參數都擁有默認值,如果想了解JVM的內存分配策略,最好手動設置這些啟動參數。再通過JDK提供的工具的統計結果,進行對比,就比較容易理解這些內存分配的理論知識。運行環境是win7 32位操作系統,JDK1.7.0_60版本。
測試代碼和JVM啟動參數如下:
public class Test { public static void main(String[] args) { int a = 0; while (true) { a++; } } }
-Xms=200M -Xmx200M -XX:NewSize=100M -Xmn100M -XX:SurvivorRatio=8
-XX:OldSize=60M -XX:PermSize=50M -XX:MaxPermSize=50M
運行上述代碼,通過jps命令獲取到進程pid,然后通過jmap -heap pid就可以查看內存分配和使用情況。
>jmap -heap 8912 Attaching to process ID 8912, please wait... Debugger attached successfully. Client compiler detected. JVM version is 24.60-b09 using thread-local object allocation. Mark Sweep Compact GC Heap Configuration: MinHeapFreeRatio = 40 MaxHeapFreeRatio = 70 MaxHeapSize = 209715200 (200.0MB) NewSize = 104857600 (100.0MB) MaxNewSize = 104857600 (100.0MB) OldSize = 62914560 (60.0MB) NewRatio = 3 SurvivorRatio = 8 PermSize = 52428800 (50.0MB) MaxPermSize = 52428800 (50.0MB)
這里顯示的堆配置參數,都可以通過JVM啟動參數來設置。下面來解釋下幾個重要參數的含義:
-Xms 和 -Xmx (-XX:InitialHeapSize 和 -XX:MaxHeapSize):指定JVM初始占用的堆內存和最大堆內存。JVM也是一個軟件,也必須要獲取本機的物理內
存,然后JVM會負責管理向操作系統申請到的內存資源。JVM啟動的時候會向操作系統申請 -Xms 設置的內存,JVM啟動后運行一段時間,如果發現內存空間
不足,會再次向操作系統申請內存。JVM能夠獲取到的最大堆內存是-Xmx設置的值。
-XX:NewSize 和 -Xmn(-XX:MaxNewSize):指定JVM啟動時分配的新生代內存和新生代最大內存。
-XX:SurvivorRatio:設置新生代中1個Eden區與1個Survivor區的大小比值。在hotspot虛擬機中,新生代 = 1個Eden + 2個Survivor。如果新生代內存是
10M,SurvivorRatio=8,那么Eden區占8M,2個Survivor區各占1M。
-XX:NewRatio:指定老年代/新生代的堆內存比例。在hotspot虛擬機中,堆內存 = 新生代 + 老年代。如果-XX:NewRatio=4表示年輕代與年老代所占比值為1:4,年輕代占整個堆內存的1/5。在設置了-XX:MaxNewSize的情況下,-XX:NewRatio的值會被忽略,老年代的內存=堆內存 - 新生代內存。老年代的最大內存 = 堆內存 - 新生代 最大內存。
-XX:OldSize:設置JVM啟動分配的老年代內存大小,類似於新生代內存的初始大小-XX:NewSize。
-XX:PermSize 和 -XX:MaxPermSize:指定JVM中的永久代(方法區)的大小。可以看到:永久代不屬於堆內存,堆內存只包含新生代和老年代。
可以發現:堆內存、新生代內存、老年代內存、永久代內存,都有一個初始內存,還有一個最大內存。下面以老年代的初始內存和最大內存為例,看下內存變化的效果,其他的應該類似。測試代碼如下:
public class TurnedTest { private static List<string> list = new ArrayList<string>(); public static void main(String[] args) { int a = 0; while (true) { a++; list.add("demo"); } } }
顯然這個程序存在內存泄露,最終會占滿整個堆內存,拋出OOM。為了看清楚這個演變的過程,我們在while循環中添加一個斷點,設置breakpoint properties中的"hit count"為100000,以debug模式運行上面的程序,然后使用jmap觀察內存占用情況。
tenured generation: capacity = 62914560 (60.0MB) used = 0 (0.0MB) free = 62914560 (60.0MB) 0.0% used tenured generation: capacity = 62914560 (60.0MB) used = 16409080 (15.648918151855469MB) free = 46505480 (44.35108184814453MB) 26.08153025309245% used tenured generation: capacity = 62914560 (60.0MB) used = 53329496 (50.858970642089844MB) free = 9585064 (9.141029357910156MB) 84.76495107014973% used tenured generation: capacity = 104857600 (100.0MB) used = 84217880 (80.3164291381836MB) free = 20639720 (19.683570861816406MB) 80.3164291381836% used
可以發現老年代內存從最開始的60M,擴大到最大值100M。