欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

jvm之内存申请过程分析

程序员文章站 2022-07-06 11:30:39
...
----------~开篇分享一句话:【纸上得来终觉浅,绝知此事要躬行】~---------------------------------------
前置了解知识: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区 8192*65/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));