深入理解 JAVA 虚拟机原理之内存分配策略(二)
public static void testAllocation() {byte[] allocation1, allocation2, allocation3, allocation4;allocation1 = new byte[2 * _1MB];allocation2 = new byte[2 * _1MB];allocation3 = new byte[2 * _1MB];allocation4 = new byte[4 * _1MB]; /出现一次 Minor GC
}
public static void main(String[] args) {testAllocation();}}
结果:
[GC (Allocation Failure) Disconnected from the target VM, address: '127.0.0.1:61454', transport: 'socket'[PSYoungGen: 6570K->704K(9216K)] 6570K->4808K(19456K), 0.0036571 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]HeapPSYoungGen total 9216K, used 7253K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)eden space 8192K, 79% used [0x00000007bf600000,0x00000007bfc657a0,0x00000007bfe00000)from space 1024K, 68% used [0x00000007bfe00000,0x00000007bfeb0000,0x00000007bff00000)to space 1024K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007c0000000)ParOldGen total 10240K, used 4104K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)object space 10240K, 40% used [0x00000007bec00000,0x00000007bf002020,0x00000007bf600000)Metaspace used 3104K, capacity 4568K, committed 4864K, reserved 1056768Kclass space used 338K, capacity 392K, committed 512K, reserved 1048576K
从输出结果可以清晰看到 “eden space 8192K、from space 1024K、to space 1024K”新生代总可用空间为 9216KB (Eden 区空间大小 + 1 个 Survivor 区的总容量)
这次 GC 发生的原因是给 allocation4 对象分配内存的时候,发现 Eden 区已经被占用了 6MB,剩余空间已经不足以分配 4MB 的内存,因此发生了 MinorGC。GC 期间有发现已有的 3 个 2MB 大小的对象已经无法全部放入 Survivor 空间(只有 1MB 大小),所以只好通过分配担保机制提前将这三个对象转移到老年代去了。
2、大对象直接进入老年代
所谓大对象是指,需要大量连续内存空间的 Java 对象,经常出现大对象容易导致内存还有不少空间时就提前触发垃圾收集以获取足够的连续空间来为大对象分配内存。
虚拟机提供了一个-XX:PretenureSizeThreshold 参数,让大于该值得对象直接进入老年代。这样做的目的是避免在新生代 Eden 区及两个 Survivor 区之间发生大量的内存复制。
PretenureSieThreshold 参数只对 Serial 和 ParNew 两款收集器有效,Parallel Scavenge 收集器不识别这个参数,并且该收集器一般不需要设置。如果必须使用此参数的场合,可以考虑 ParNew 加 CMS 的收集器组合。
jdk1.7 默认垃圾收集器 Parallel Scavenge(新生代)+Parallel Old(老年代)
jdk1.8 默认垃圾收集器 Parallel Scavenge(新生代)+Parallel Old(老年代)
jdk1.9 默认垃圾收集器 G1
-XX:+PrintCommandLineFlags 可查看默认设置收集器类型
-XX:+PrintGCDetails 打印的 GC 日志
package com.lkf.jvm;
public class PretenureSizeThresholdDemo {private static final int _1MB = 1024 * 1024;
/**
VM 参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
-XX:+PrintCommandLineFlags -XX:PretenureSizeThreshold=3145728 -XX:+UseSerialGC
因为使用的是 jdk1.8,所以此处特指定了使用垃圾收集器 Serial
大于 3M 的对象直接进入老年代*/public static void testPretenureSizeThreshold() {byte[] allocation;allocation = new byte[4 * _1MB];//直接分配在老年代}
public static void main(String[] args) {testPretenureSizeThreshold();}}
结果:
Heapdef new generation total 9216K, used 2643K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)eden space 8192K, 32% used [0x00000007bec00000, 0x00000007bee94ee8, 0x00000007bf400000)from space 1024K, 0% used [0x00000007bf400000, 0x00000007bf400000, 0x00000007bf500000)to space 1024K, 0% used [0x00000007bf500000, 0x00000007bf500000, 0x00000007bf600000)tenured generation total 10240K, used 4096K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)the space 10240K, 40% used [0x00000007bf600000, 0x00000007bfa00010, 0x00000007bfa00200, 0x00000007c0000000)Metaspace used 3104K, capacity 4568K, committed 4864K, reserved 1056768Kclass space used 338K, capacity 392K, committed 512K, reserved 1048576K
3、长期存活的对象将进入老年代
虚拟机使用了分代收集的思想来管理内存,内存回收时为了区分哪些对象应放在新生代,哪些应该放在老年代,虚拟机为每个对象定义了一个对象年龄(Age)计数器。
如果对象被分配在 Eden 区并经过第一次 Minor GC 后仍然存活,并且能被 Survivor 容乃的情况下,将被移动到 Survivor 中,对象年龄设为 1。在 Survivor 区每经过一次 Minor GC,年龄就加 1,当对象的年龄到达一定程度时(默认 15 岁),就会晋升到老年代。对象晋升到老年代的阈值,可以通过参数:-XX:MaxTenuringThreshold 设置。
package com.lkf.jvm;
/**
长期存活对象将进入老年代*/public class MaxTenuringThresholdDemo {private static final int _1MB = 1024 * 1024;
/**
VM 参 数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=1 -XX:+PrintTenuringDistribution
*/public static void testTenuringThreshold() {byte[] allocation1, allocation2, allocation3;allocation1 = new byte[_1M
B / 4];//什么时候进入老年代取决于 XX:MaxTenuringThreshold 设置 allocation2 = new byte[4 * _1MB];allocation3 = new byte[4 * _1MB];allocation3 = null;allocation3 = new byte[4 * _1MB];}
public static void main(String[] args) {testTenuringThreshold();}}
-XX:MaxTenuringThreshold=1 (jdk1.8)运行结果:
HeapPSYoungGen total 9216K, used 6994K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)eden space 8192K, 85% used [0x00000007bf600000,0x00000007bfcd4b68,0x00000007bfe00000)from space 1024K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007c0000000)to space 1024K, 0% used [0x00000007bfe00000,0x00000007bfe00000,0x00000007bff00000)ParOldGen total 10240K, used 8192K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)object space 10240K, 80% used [0x00000007bec00000,0x00000007bf400020,0x00000007bf600000)Metaspace used 3104K, capacity 4568K, committed 4864K, reserved 1056768Kclass space used 338K, capacity 392K, committed 512K, reserved 1048576K
-XX:MaxTenuringThreshold=15 (jdk1.8)运行结果:
HeapPSYoungGen total 9216K, used 6994K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)eden space 8192K, 85% used [0x00000007bf600000,0x00000007bfcd4b68,0x00000007bfe00000)from space 1024K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007c0000000)to space 1024K, 0% used [0x00000007bfe00000,0x00000007bfe00000,0x00000007bff00000)ParOldGen total 10240K, used 8192K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)object space 10240K, 80% used [0x00000007bec00000,0x00000007bf400020,0x00000007bf600000)Metaspace used 3104K, capacity 4568K, committed 4864K, reserved 1056768Kclass space used 338K, capacity 392K, committed 512K, reserved 1048576K
4、动态对象年龄判定
虚拟机并不是永远要求对象的年龄必须达到了 MaxTenuringThreshold 才能晋升老年代,如果 Survivor 空间中,相同年龄对象的大小之和大于 Survivor 空间大小的一半,就可以直接进入老年代。
package com.lkf.jvm;
/**
动态对象年龄判定*/public class TenuringThresholdDemo {private static final int _1MB = 1024 * 1024;
/**
VM 参 数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15 -XX:+PrintTenuringDistribution -XX:+UseSerialGC
*/public static void testTenuringThreshold() {byte[] allocation1, allocation2, allocation3, allocation4;allocation1 = new byte[_1MB / 4];allocation2 = new byte[_1MB / 4];//allocation1+allocation2 大于 Survivo 空间的一半
评论