深入理解_JVM内存管理内存分配和回收策略06

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入理解_JVM内存管理内存分配和回收策略06相关的知识,希望对你有一定的参考价值。

解决两个问题:
     1、对象分配内存;
     2、回收分配给对象的内存。
本节详细讲解分配的问题:
名词解释:
新生代GC(Minor GC):指发生在新生代的垃圾回收动作,非常频繁,回收速度很快。
老生代GC(Major GC/Full GC):指发生在老生代的垃圾回收动作,出现了Major GC,经常会伴随至少一次的Minor GC(但非绝对),速度一般会比Minor GC慢10倍。
打印日志说明:
<1> DefNew:串行GC方式(Serial GC)。
<2> ParNew:ParNew方式。
<3> PSYoungGen:Parallel Scavenge方式。
大原则:
     (1)对象优先分配在新生代的Eden上;
     (2)大对象直接进入老生代;
     (3)长期存活对象将进入老生代;
     (4)动态对象年龄判断;
     (5)空间分配担保。
 
实例演示:
1、对象优先分配在新生代的Eden上,通过-XX:+PrintGCDetails收集器日志参数,打印内存回收日志。
/*
 * 参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
 */
public class testAllocation {
        private static final int _1MB=1024*1024;
        public static void main(String[] args) {
               byte[] allocation1, allocation2,allocation3 ,allocation4;
              allocation1 = new byte [2*_1MB];
              allocation2 = new byte [2*_1MB];
              allocation3 = new byte [2*_1MB];
              allocation4 = new byte [2*_1MB]; //出现一次Minor GC
       }
}
运行结果:
[GC [DefNew: 6808K->434K(9216K), 0.0062476 secs] 6808K->6578K(19456K), 0.0062850 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
Heap
 def new generation   total 9216K, used 4857K [0x04770000, 0x05170000, 0x05170000)
  eden space 8192K,  54% used [0x04770000, 0x04bc1fa8, 0x04f70000)
  from space 1024K,  42% used [0x05070000, 0x050dc810, 0x05170000)
  to   space 1024K,   0% used [0x04f70000, 0x04f70000, 0x05070000)
 tenured generation   total 10240K, used 6144K [0x05170000, 0x05b70000, 0x05b70000)
   the space 10240K,  60% used [0x05170000, 0x05770030, 0x05770200, 0x05b70000)
 compacting perm gen  total 12288K, used 1589K [0x05b70000, 0x06770000, 0x09b70000)
   the space 12288K,  12% used [0x05b70000, 0x05cfd758, 0x05cfd800, 0x06770000)
运行结果说明:
(1)[Times: user=0.02 sys=0.00, real=0.01 secs]
表示Minor GC占用的CPU user和sys的百分比,以及消耗的共时间。
 
(2)[GC [DefNew: 6808K->434K(9216K), 0.0062476 secs]
 DefNew:新生代回收前:6808K,新生代回收后:434K 新生代可用空间为:Eden+Survivor。
Heap总量回收前:6808K,Heap总量回收后:6578K,Heap可用空间:19456K(Eden+Survivor+老年代)
 
(3)详解分解:
     <1> 发现新生代虽然变化很大,但是Heap却没有太大变化。
     <2> 程序给a1,a2,a3分配完空间之后,Eden只剩下2MB,而a4有4MB,不够,所以发生一次Minor GC。
     <3> Minor GC之后,a1,a2,a3进入老生代,因为to(Survivor)无法装下他们中的任何一个。此时,tenured generation:the space 10240K,  60%。
     <4> a4便进入了了Eden:eden space 8192K,  54% used。
 
2、变更一下实例1。
/*
 * 参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
 */
public class testAllocation_B {
        private static final int _1MB=1024*1024;
        public static void main(String[] args) {
               byte[] allocation1, allocation2,allocation3 ,allocation4;
              allocation1 = new byte [4*_1MB];
              allocation2 = new byte [(int)0.8*_1MB];
              allocation3 = new byte [(int)0.8*_1MB];
              allocation4 = new byte [(int)0.8*_1MB]; 
       }
}
Heap
 def new generation   total 9216K, used 5088K [0x04800000, 0x05200000, 0x05200000)
  eden space 8192K,  62% used [0x04800000, 0x04cf8100, 0x05000000)
  from space 1024K,   0% used [0x05000000, 0x05000000, 0x05100000)
  to   space 1024K,   0% used [0x05100000, 0x05100000, 0x05200000)
 tenured generation   total 10240K, used 0K [0x05200000, 0x05c00000, 0x05c00000)
   the space 10240K,   0% used [0x05200000, 0x05200000, 0x05200200, 0x05c00000)
 compacting perm gen  total 12288K, used 1616K [0x05c00000, 0x06800000, 0x09c00000)
   the space 12288K,  13% used [0x05c00000, 0x05d94390, 0x05d94400, 0x06800000)
No shared spaces configured.
 
3、大对象直接进入老生代。
     多大的对象算大对象?通过参数-XX:PretenureSizeThreshold来控制,出现大对象容易导致内存还有不少空间时就触发GC来寻求连续的空间来安置他们。
     注意:
     <1> 该参数不能写成-Xms 10M这样,需要精确到B,如下例子。
     <2> 该参数只对Serial和ParNew两款收集器有效,Parallel Scavenge收集器不认识该参数。如非要设置该参数,考虑ParNew+CMS的组合。
例子说明:
     增加-XX:PretenureSizeThreshold=3145728参数,控制大于3MB的对象直接进入老生代。
/**
* -verbose:gc - Xms20M -Xmx20M -Xmn10M - XX:+PrintGCDetails
* -XX:SurvivorRatio=8 -XX:PretenureSizeThreshold=3145728
*/
public class testPretenureSizeThreshold {
        private static final int _1MB = 1024*1024;
        public static void main(String[] args) {
               byte[]a;
              a = new byte [4*_1MB];
       }
}
Heap
 def new generation   total 9216K, used 991K [0x047d0000, 0x051d0000, 0x051d0000)
  eden space 8192K,  12% used [0x047d0000, 0x048c7f00, 0x04fd0000)
  from space 1024K,   0% used [0x04fd0000, 0x04fd0000, 0x050d0000)
  to   space 1024K,   0% used [0x050d0000, 0x050d0000, 0x051d0000)
 tenured generation   total 10240K, used 4096K [0x051d0000, 0x05bd0000, 0x05bd0000)
   the space 10240K,  40% used [0x051d0000, 0x055d0010, 0x055d0200, 0x05bd0000)
 compacting perm gen  total 12288K, used 1616K [0x05bd0000, 0x067d0000, 0x09bd0000)
   the space 12288K,  13% used [0x05bd0000, 0x05d64348, 0x05d64400, 0x067d0000)
No shared spaces configured.
运行结果说明:
<1> 新生代基本没有被使用。
<2> 老生代直接占用百分之四十,说明对象直接进入老生代。
 
4、长期存活的对象将进入老年代。
问题:为了保证垃圾回收时能识别哪些对象分别放在什么代上?
解决办法:虚拟机给每个对象定义了一个对象年龄(Age)计数器。
 
具体算法:
<1> 如果对象在Eden出生并经过第一次Minor GC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并将对象年龄设置为1。
<2> 对象在Survivor区中每熬过一次Minor GC,年龄就增加1岁,当它的年龄增加到一定程度(默认15岁)收,就会被晋升到老年代中。
<3> 对象晋升到老年代的年龄阀值,可以通过参数:-XX:MaxTenuringThreshold来设置。
 
例子说明:
     将-XX:MaxTenuringThreshold分别设置为1和15,来验证是否到-XX:MaxTenuringThreshold阀值之后,进入老年代。
/*
 * -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
 * -XX:MaxTenuringThreshold=1 -XX:+PrintTenuringDistribution
 */
public class testTenuringThreshold {
        private static final int _1MB = 1024*1024;
        public static void main(String[] args) {
               byte[] allocation1, allocation2,allocation3 ,allocation4;
              allocation1 = new byte [_1MB/4];
              allocation2 = new byte [4*_1MB];
              allocation3 = new byte [4*_1MB];
              allocation3 = null;
              allocation4 = new byte [4*_1MB];
       }
}
运行说明:
以下结果是以-XX:MaxTenuringThreshold=1运行的。
[GC [DefNew
Desired survivor size 524288 bytes, new threshold 1 (max 1)
- age   1:     706640 bytes,     706640 total
: 5015K->690K(9216K), 0.0055151 secs] 5015K->4786K(19456K), 0.0055536 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[GC [DefNew
Desired survivor size 524288 bytes, new threshold 1 (max 1)
- age   1:        248 bytes,        248 total
: 5113K->0K(9216K), 0.0019266 secs] 9210K->4786K(19456K), 0.0019567 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
 def new generation   total 9216K, used 4260K [0x04840000, 0x05240000, 0x05240000)
  eden space 8192K,  52% used [0x04840000, 0x04c68fd8, 0x05040000)
  from space 1024K,   0% used [0x05040000, 0x050400f8, 0x05140000)
  to   space 1024K,   0% used [0x05140000, 0x05140000, 0x05240000)
 tenured generation   total 10240K, used 4785K [0x05240000, 0x05c40000, 0x05c40000)
   the space 10240K,  46% used [0x05240000, 0x056ec790, 0x056ec800, 0x05c40000)
 compacting perm gen  total 12288K, used 1589K [0x05c40000, 0x06840000, 0x09c40000)
   the space 12288K,  12% used [0x05c40000, 0x05dcd758, 0x05dcd800, 0x06840000)
No shared spaces configured.
<1> 从上面红色字体可以看出,from space 1024K,   0% used,而the space 10240K,  46% used。
 
5、动态对象年龄判断:
     并不是一定要达到MaxTenuringThreshold对象才会进入老年代。
     如果在Survivor空间中相同年龄所有对象大小的总和>Survivor空间的一半,年龄>=该年龄的对象就可以直接进入老年代。
例子:
 
6、空间分配担保
     发生Minor GC时,虚拟机会检测之前每次晋升到老生代的平均大小是否大于老生代的剩余空间:
     如果大于,则改为直接进行一次Full GC。
     如果小于,则查看HandlePromotionFailure设置是否允许担保失败:
          如果允许,则只会进行一次Minor GC;
          如果不允许,则改为执行一次Full GC。
     在实际完成内存回收之前是无法明确知道的,所以只好取之前每次回收到晋升老生代对象容量的平均大小值作为经验值。由于平均值仍然是动态的,可能导致担保失败(HandlePromotionFailure),那就只好在失败之后重新发起一次Full GC。
     虽然担保失败绕的圈子是最大的,但大部分情况下都还是会将HandlePromotionFailure开关打开,避免Full GC过于频繁。 

以上是关于深入理解_JVM内存管理内存分配和回收策略06的主要内容,如果未能解决你的问题,请参考以下文章

jvm,深入理解java虚拟机,内存分配与回收策略

jvm,深入理解java虚拟机,内存分配与回收策略

深入理解JVM-垃圾收集器与内存分配策略

深入理解Java虚拟机-Java内存区域,垃圾回收机制和内存分配策略

深入理解Java虚拟机-Java内存区域,垃圾回收机制和内存分配策略

《深入理解JVM——GC算法与内存分配策略》