1. 程式人生 > >(入門貼)JVM堆記憶體相關的啟動引數:年輕代、老年代和永久代的記憶體分配

(入門貼)JVM堆記憶體相關的啟動引數:年輕代、老年代和永久代的記憶體分配

如果想觀察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++;
        }
    }
}
-Xms200M  -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。