Jvm-内存申请过程分析

jopen 9年前

前置了解知识: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