Jvm-內存申請過程分析

jopen 9年前發布 | 16K 次閱讀 Java開發 JVM

前置了解知識:http://wangxinchun.iteye.com/blog/2189321

內存申請過程
1、JVM會試圖為相關Java對象在Eden中初始化一塊內存區域;
2、當Eden空間足夠時,內存申請結束。否則到下一步;
3、JVM試圖釋放在Eden中所有不活躍的對象(minor collection),釋放后若Eden空間仍然不足以放入新對象,則試圖將部分Eden中活躍對象放入Survivor區;
4、Survivor區被用來作為Eden及old的中間交換區域,如果Survivor不足以放置eden區的對象,如果old區有空閑,那么直接放置在old區,Survivor區的對象會被移到Old區
5、當old區空間不夠時,JVM會在old區進行major collection;
完全垃圾收集后,若Survivor及old區仍然無法存放從Eden復制過來的部分對象,導致JVM無法在Eden區為新對象創建內存區域,則出現"Out of memory錯誤";



1、jvm優先分配在eden區
2、當Eden空間足夠時,內存申請結束。

證明:
jvm參數設置:

-Xmx20M -Xms20M -Xmn10M -verbose:gc  -XX:SurvivorRatio=8 -XX:+PrintGCDetails

其中:
-Xmx20M : 堆最大內存是20M
-Xms20M: 初始化也是20M
-Xmn10M :新生代10M,老年代10M
-verbose:gc  輸出退出后的日志
-XX:SurvivorRatio=8:eden 為8M s0 s1 各1M
-XX:+PrintGCDetails:打印gc日志的詳情

代碼:

public class TestEden {
    public static void main(String[] args) {
        byte[] b1 = new byte[1024*1024*2];
    }
}


日志輸出:

Heap
 def new generation   total 9216K, used 2704K [0x03ac0000, 0x044c0000, 0x044c0000)
  eden space 8192K,  33% used [0x03ac0000, 0x03d64230, 0x042c0000)
  from space 1024K,   0% used [0x042c0000, 0x042c0000, 0x043c0000)
  to   space 1024K,   0% used [0x043c0000, 0x043c0000, 0x044c0000)
 tenured generation   total 10240K, used 0K [0x044c0000, 0x04ec0000, 0x04ec0000)
   the space 10240K,   0% used [0x044c0000, 0x044c0000, 0x044c0200, 0x04ec0000)
 compacting perm gen  total 12288K, used 2143K [0x04ec0000, 0x05ac0000, 0x08ec0000)
   the space 12288K,  17% used [0x04ec0000, 0x050d7fe0, 0x050d8000, 0x05ac0000)
No shared spaces configured.


通過分析main方法中申請2m的內存,內存分配到了eden區。from to tenered區都是沒有被使用。



3、JVM試圖釋放在Eden中所有不活躍的對象(minor collection),釋放后若Eden空間仍然不足以放入新對象,則試圖將部分Eden中活躍對象放入Survivor區;

證明:
jvm參數設置同上
代碼:

public class TestEden {
    public static void main(String[] args) {
        byte[] b1 = new byte[1024*1024*9/10];
        byte[] b2 = new byte[1024*1024*8*9/10];
    }
}

日志:

[GC [DefNew: 1250K->1024K(9216K), 0.0015682 secs] 1250K->1062K(19456K), 0.0016006 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap
 def new generation   total 9216K, used 8560K [0x26ea0000, 0x278a0000, 0x278a0000)
  eden space 8192K,  92% used [0x26ea0000, 0x275fc308, 0x276a0000)
  from space 1024K, 100% used [0x277a0000, 0x278a0000, 0x278a0000)
  to   space 1024K,   0% used [0x276a0000, 0x276a0000, 0x277a0000)
 tenured generation   total 10240K, used 38K [0x278a0000, 0x282a0000, 0x282a0000)
   the space 10240K,   0% used [0x278a0000, 0x278a9920, 0x278a9a00, 0x282a0000)
 compacting perm gen  total 12288K, used 366K [0x282a0000, 0x28ea0000, 0x2c2a0000)
   the space 12288K,   2% used [0x282a0000, 0x282fba28, 0x282fbc00, 0x28ea0000)
    ro space 8192K,  67% used [0x2c2a0000, 0x2c802f30, 0x2c803000, 0x2caa0000)
    rw space 12288K,  53% used [0x2caa0000, 0x2d110180, 0x2d110200, 0x2d6a0000)


分析:
程序執行邏輯 先申請了1M的內存,然后再次申請8M的內存,考慮內存本身結構會占用內存空間為了避免邊界都以9/10的比例來申請。
結果:
先分配了解決1M的內存在eden區,因為再次申請接近8M的內存時,eden區不夠,發生了一次young gc,1M的內存分配到了from區,接近8M的內存放在了eden區。

[GC [DefNew: 1250K->1024K(9216K), 0.0015682 secs] 1250K->1062K(19456K), 0.0016006 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
其中
9216K 為eden+from = 8M+1M=9M
19456K 為Xmx-to = 20M-1M = 19M
1250K->1024K :本次young gc 年輕代內存的變化
1250K->1062K :本次young gc 總堆內存的變化


4、Survivor區被用來作為Eden及old的中間交換區域,如果Survivor不足以放置eden區的對象,如果old區有空閑,那么直接放置在old區,Survivor區的對象會被移到Old區

證明:
jvm參數設置同上
程序如下:

public class TestEden {
    public static void main(String[] args) {
        byte[] b1 = new byte[1024*1024*5];
        byte[] b2 = new byte[1024*1024*5];
        System.out.println(b1+""+b2);
    }
}

日志輸出:

[GC [DefNew: 5448K->140K(9216K), 0.0051311 secs] 5448K->5260K(19456K), 0.0051644 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[B@c17164[B@1fb8ee3
Heap
 def new generation   total 9216K, used 5511K [0x26ea0000, 0x278a0000, 0x278a0000)
  eden space 8192K,  65% used [0x26ea0000, 0x273deb58, 0x276a0000)
  from space 1024K,  13% used [0x277a0000, 0x277c32a8, 0x278a0000)
  to   space 1024K,   0% used [0x276a0000, 0x276a0000, 0x277a0000)
 tenured generation   total 10240K, used 5120K [0x278a0000, 0x282a0000, 0x282a0000)
   the space 10240K,  50% used [0x278a0000, 0x27da0010, 0x27da0200, 0x282a0000)
 compacting perm gen  total 12288K, used 366K [0x282a0000, 0x28ea0000, 0x2c2a0000)
   the space 12288K,   2% used [0x282a0000, 0x282fbb28, 0x282fbc00, 0x28ea0000)
    ro space 8192K,  67% used [0x2c2a0000, 0x2c802f30, 0x2c803000, 0x2caa0000)
    rw space 12288K,  53% used [0x2caa0000, 0x2d110180, 0x2d110200, 0x2d6a0000)

分析:首先程序申請了5M的內存,放在了eden區,然后再次申請5M的內存,eden區不夠,發生young gc,5M放置在from區,依然不夠,故直接放置old 區
結果:eden區 819265/100/1024=5M
old區:10240
50/100/1024=5M

5、如果eden區不夠,from區也承載不了,恰old區也已承載不了,那么會full gc
證明:
jvm參數配置同上
java代碼:

public class TestEden {
    public static void main(String[] args) {
        byte[] b1 = new byte[1024*1024*4];
        byte[] b2 = new byte[1024*1024*5];
        b1=null;
        b2=null;
        byte[] b3 = new byte[1024*1024*8];
    }
}

日志輸出:

[GC [DefNew: 4424K->140K(9216K), 0.0056340 secs] 4424K->4236K(19456K), 0.0056837 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
[GC [DefNew: 5260K->140K(9216K), 0.0008662 secs][Tenured: 4096K->140K(10240K), 0.0084034 secs] 9356K->140K(19456K), [Perm : 366K->366K(12288K)], 0.0093567 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
Heap
 def new generation   total 9216K, used 163K [0x26ea0000, 0x278a0000, 0x278a0000)
  eden space 8192K,   2% used [0x26ea0000, 0x26ec8fc8, 0x276a0000)
  from space 1024K,   0% used [0x276a0000, 0x276a0000, 0x277a0000)
  to   space 1024K,   0% used [0x277a0000, 0x277a0000, 0x278a0000)
 tenured generation   total 10240K, used 8332K [0x278a0000, 0x282a0000, 0x282a0000)
   the space 10240K,  81% used [0x278a0000, 0x280c3228, 0x280c3400, 0x282a0000)
 compacting perm gen  total 12288K, used 366K [0x282a0000, 0x28ea0000, 0x2c2a0000)
   the space 12288K,   2% used [0x282a0000, 0x282fba68, 0x282fbc00, 0x28ea0000)
    ro space 8192K,  67% used [0x2c2a0000, 0x2c802f30, 0x2c803000, 0x2caa0000)
    rw space 12288K,  53% used [0x2caa0000, 0x2d110180, 0x2d110200, 0x2d6a0000)


分析:
1、先申請了4M內存,放在eden區。
2、然后申請5M內存時,發生了young gc,無奈4M被安置在了old區,5M放在了eden區
3、再次向eden區申請8M的時候,eden區不夠,同時8M還是放不下eden區,于是要full gc,full gc前先進行了young gc,5M 因為沒有引用被清理,8M放在old區時,old區滿,故full gc 4M也被清除old區。
結果:10240K,  81% used  為8M放在了old區


如果old區不夠,會發生錯誤 java.lang.OutOfMemoryError: Java heap space

public class TestEden {
    public static void main(String[] args) {
        byte[] b1 = new byte[1024*1024*4];
        byte[] b2 = new byte[1024*1024*5];
        b2=null;
        byte[] b3 = new byte[1024*1024*8];
    }
}

日志:

[GC [DefNew: 4424K->140K(9216K), 0.0057531 secs] 4424K->4236K(19456K), 0.0058056 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[GC [DefNew: 5260K->140K(9216K), 0.0008379 secs][Tenured: 4096K->4236K(10240K), 0.0092434 secs] 9356K->4236K(19456K), [Perm : 366K->366K(12288K)], 0.0101605 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 
[Full GC [Tenured: 4236K->4233K(10240K), 0.0075081 secs] 4236K->4233K(19456K), [Perm : 366K->365K(12288K)], 0.0075750 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at test.test.TestEden.main(TestEden.java:8)
Heap
 def new generation   total 9216K, used 251K [0x26ea0000, 0x278a0000, 0x278a0000)
  eden space 8192K,   3% used [0x26ea0000, 0x26edefd0, 0x276a0000)
  from space 1024K,   0% used [0x276a0000, 0x276a0000, 0x277a0000)
  to   space 1024K,   0% used [0x277a0000, 0x277a0000, 0x278a0000)
 tenured generation   total 10240K, used 4233K [0x278a0000, 0x282a0000, 0x282a0000)
   the space 10240K,  41% used [0x278a0000, 0x27cc2630, 0x27cc2800, 0x282a0000)
 compacting perm gen  total 12288K, used 365K [0x282a0000, 0x28ea0000, 0x2c2a0000)
   the space 12288K,   2% used [0x282a0000, 0x282fb688, 0x282fb800, 0x28ea0000)
    ro space 8192K,  67% used [0x2c2a0000, 0x2c802f30, 0x2c803000, 0x2caa0000)
    rw space 12288K,  53% used [0x2caa0000, 0x2d110180, 0x2d110200, 0x2d6a0000)


分析:
4M 先是放在eden區,5M申請時gc 4M轉入old區,5M留在了eden區,8M申請時,發生gc5M在eden區被清理,因為eden區不夠8M,所以分配到old區,old區發生full gc,full gc依然騰不出空間,報錯。

注意:5M是在eden區gc時被清理,理由:[GC [DefNew: 5260K->140K(9216K), 0.0008379 secs][Tenured: 4096K->4236K(10240K), 0.0092434 secs] 9356K->4236K(19456K), [Perm : 366K->366K(12288K)], 0.0101605 secs] [Times: user=0.01 sys=0.00, real=0.01 secs] 日志顯示(Tenured: 4096K->4236K(10240K))無變化,但是eden區和堆總內存變化(DefNew: 5260K->140K(9216K), 9356K->4236K(19456K));

來自:http://wangxinchun.iteye.com/blog/2190330

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!