既然虚拟机采用了分代收集的思想来管理内存,那么内存回收时,就必须能识别哪些对象应放在新生代,哪些对象应放在老年代中。为了做到这点,虚拟机给每个对象定义了一个对象年龄(Age)计数器。如果对象在Eden出生并经过第一次Minor GC后仍然存活,并且能别Survivor容纳的话,将被移动到Survivor空间中,并且对象年龄设为1。对象在Survivor区中每“熬过”一次Minor GC,年龄就增加1岁,当它的年龄增加到一定程度(默认为15岁),就将会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数-XX:MaxTenuringThreshold设置。
读者可以试试分别以-XX:MaxTenuringThreshold=1和-XX:MaxTenuringThreshold=15两种设置来执行代码清单3-7中的testTenuringThreshold()方法,此方法中的allocation1对象需要256KB内存,Survivor空间可以容纳。当MaxTenuringThreshold=1时,allocation1对象在第二次GC发生时进入老年代,新生代已使用的内存GC后非常干净地变成0KB。而MaxTenuringThreshold=15时,第二次GC发生后,allocation1对象则还留在新生代Survivor空间,这时新生代仍然有404KB被占用。
代码清单3-7 长期存活的对象进入老年代
package lime.jvm._003._006._003;
/**
* @Author : Liangmy
* @Description :
* @Date : Created in 2020/1/5 下午12:56
* @Modified By :
* <p>
* 代码清代3-7 长期存活的对象进入老年代
* <p>
* VM Args : -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=1 -verbose:gc -XX:+PrintGCDetails -XX:+PrintTenuringDistribution -XX:+UseSerialGC
* java -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=1 -verbose:gc -XX:+PrintGCDetails -XX:+PrintTenuringDistribution -XX:+UseSerialGC lime.jvm._003._006._003.TestTenuringThreshold
* java -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15 -verbose:gc -XX:+PrintGCDetails -XX:+PrintTenuringDistribution -XX:+UseSerialGC lime.jvm._003._006._003.TestTenuringThreshold
* <p>
*/
public class TestTenuringThreshold {
private static final int _1MB = 1024 * 1024;
public static void main(String[] args) {
testTenuringThreshold();
}
private static void testTenuringThreshold() {
byte[] allocation1, allocation2, allocation3;
allocation1 = new byte[_1MB / 4];
// 什么时候进入老年代取决于XX:MaxTenuringThreshold设置
allocation2 = new byte[4 * _1MB];
allocation3 = new byte[4 * _1MB];
allocation3 = null;
allocation3 = new byte[4 * _1MB];
}
}
以MaxTenuringThreshold=1参数来运行的结果:
localhost:_003 liangmy$ java -version java version "1.8.0_151" Java(TM) SE Runtime Environment (build 1.8.0_151-b12) Java HotSpot(TM) 64-Bit Server VM (build 25.151-b12, mixed mode) localhost:_003 liangmy$ java -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=1 -verbose:gc -XX:+PrintGCDetails -XX:+PrintTenuringDistribution -XX:+UseSerialGC lime.jvm._003._006._003.TestTenuringThreshold [GC (Allocation Failure) [DefNew Desired survivor size 524288 bytes, new threshold 1 (max 1) - age 1: 547200 bytes, 547200 total : 5023K->534K(9216K), 0.0049281 secs] 5023K->4630K(19456K), 0.0049702 secs] [Times: user=0.01 sys=0.01, real=0.01 secs] [GC (Allocation Failure) [DefNew Desired survivor size 524288 bytes, new threshold 1 (max 1) : 4630K->0K(9216K), 0.0015036 secs] 8726K->4628K(19456K), 0.0015442 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Heap def new generation total 9216K, used 4178K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000) eden space 8192K, 51% used [0x00000007bec00000, 0x00000007bf014930, 0x00000007bf400000) from space 1024K, 0% used [0x00000007bf400000, 0x00000007bf400000, 0x00000007bf500000) to space 1024K, 0% used [0x00000007bf500000, 0x00000007bf500000, 0x00000007bf600000) tenured generation total 10240K, used 4628K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000) the space 10240K, 45% used [0x00000007bf600000, 0x00000007bfa85280, 0x00000007bfa85400, 0x00000007c0000000) Metaspace used 2646K, capacity 4486K, committed 4864K, reserved 1056768K class space used 286K, capacity 386K, committed 512K, reserved 1048576K |
以MaxTenuringThreshold=15参数来运行的结果:
以MaxTenuringThreshold=15参数来运行的结果(测试结果不一致):
localhost:_003 liangmy$ java -version java version "1.8.0_151" Java(TM) SE Runtime Environment (build 1.8.0_151-b12) Java HotSpot(TM) 64-Bit Server VM (build 25.151-b12, mixed mode) localhost:_003 liangmy$ java -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15 -verbose:gc -XX:+PrintGCDetails -XX:+PrintTenuringDistribution -XX:+UseSerialGC lime.jvm._003._006._003.TestTenuringThreshold [GC (Allocation Failure) [DefNew Desired survivor size 524288 bytes, new threshold 1 (max 15) - age 1: 547200 bytes, 547200 total : 5023K->534K(9216K), 0.0058685 secs] 5023K->4630K(19456K), 0.0059285 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] [GC (Allocation Failure) [DefNew Desired survivor size 524288 bytes, new threshold 15 (max 15) : 4630K->0K(9216K), 0.0015947 secs] 8726K->4628K(19456K), 0.0016348 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] Heap def new generation total 9216K, used 4178K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000) eden space 8192K, 51% used [0x00000007bec00000, 0x00000007bf014930, 0x00000007bf400000) from space 1024K, 0% used [0x00000007bf400000, 0x00000007bf400000, 0x00000007bf500000) to space 1024K, 0% used [0x00000007bf500000, 0x00000007bf500000, 0x00000007bf600000) tenured generation total 10240K, used 4628K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000) the space 10240K, 45% used [0x00000007bf600000, 0x00000007bfa85280, 0x00000007bfa85400, 0x00000007c0000000) Metaspace used 2646K, capacity 4486K, committed 4864K, reserved 1056768K class space used 286K, capacity 386K, committed 512K, reserved 1048576K |
第二次GC时候仍然是GC掉了。
愁人。。。
引用1:读《深入理解jvm虚拟机》之长期存活对象进入老年代,有感!!!!
引用2:java虚拟机长期存活的对象将进入老年代实验结果与书上不同?
引用3:MaxTenuringThreshold 和 TargetSurvivorRatio参数说明
总结:
Desired survivor size 524288 bytes
关于这个512KB空间是怎么来的,JVM有这样一个参数:-XX:TargetSurvivorRatio:目标存活率,默认为50%,表明所有age的survivor space对象的大小如果超过Desired survivor size,则重新计算threshold,以age和MaxTenuringThreshold的最小值为准,否则以MaxTenuringThreshold为准。
-XX:TargetSurvivorRatio
设定survivor区的目标使用率。默认50,即survivor区对象目标使用率为50%。
计算公式: (survivor_capacity * TargetSurvivorRatio) / 100 * sizeof(a pointer):survivor_capacity(一个survivor space的大小)乘以TargetSurvivorRatio,表明所有age的survivor space对象的大小如果超过Desired survivor size,则重新计算threshold,以age和MaxTenuringThreshold的最小值为准,否则以MaxTenuringThreshold为准。
JVM会将每个对象的年龄信息、各个年龄段对象的总大小记录在“age table”表中。基于“age table”、survivor区大小、survivor区目标使用率(-XX:TargetSurvivorRatio)、晋升年龄阈值(-XX:MaxTenuringThreshold),JVM会动态的计算tenuring threshold的值。一旦对象年龄达到了tenuring threshold就会晋升到老年代。
为什么要动态的计算tenuring threshold的值呢?假设有很多年龄还未达到TenuringThreshold的对象依旧停留在survivor区,这样不利于新对象从eden晋升到survivor。因此设置survivor区的目标使用率,当使用率达到时重新调整TenuringThreshold值,让对象尽早的去old区。
Survivor空间中相同年龄所有对象大小的总和(meta-class大小)大于Survivor空间的一半导致的提前进入老年区。主要是因为class-path也占用了大小。
提升Survivor空间或者减小allocation1的空间之后就跟书上一致了。
1* 提升Survivor空间
package lime.jvm._003._006._003;
/**
* @Author : Liangmy
* @Description :
* @Date : Created in 2020/1/5 下午12:56
* @Modified By :
* <p>
* 代码清代3-7 长期存活的对象进入老年代
* <p>
* VM Args : -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=1 -verbose:gc -XX:+PrintGCDetails -XX:+PrintTenuringDistribution -XX:+UseSerialGC
* java -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=1 -verbose:gc -XX:+PrintGCDetails -XX:+PrintTenuringDistribution -XX:+UseSerialGC lime.jvm._003._006._003.TestTenuringThreshold
* java -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15 -verbose:gc -XX:+PrintGCDetails -XX:+PrintTenuringDistribution -XX:+UseSerialGC lime.jvm._003._006._003.TestTenuringThreshold
* 1* 提升Survivor空间 : java -Xms80M -Xmx80M -Xmn40M -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15 -verbose:gc -XX:+PrintGCDetails -XX:+PrintTenuringDistribution -XX:+UseSerialGC lime.jvm._003._006._003.TestTenuringThreshold
*
* <p>
*/
public class TestTenuringThreshold {
private static final int _1MB = 1024 * 1024;
public static void main(String[] args) {
testTenuringThreshold();
}
private static void testTenuringThreshold() {
byte[] allocation1, allocation2, allocation3;
allocation1 = new byte[_1MB]; // (eden(32MB) allocation1 = 1MB, from survivor(4MB) 0MB, to survivor(4MB) 0MB, tenure gen(40MB) 0MB)
// 什么时候进入老年代取决于XX:MaxTenuringThreshold设置
allocation2 = new byte[16 * _1MB]; // (eden(32MB) allocation1 = 1MB, allocation2 = 16MB,from survivor(4MB) 0MB, to survivor(4MB) 0MB, tenure gen(40MB) 0MB)
// 1MB + 16MB + 16MB = 33MB > eden(32MB) 触发Minor GC
// Desired survivor size = (from survivor(4MB) * TargetSurvivorRatio) / 100 * sizeof(a pointer) = (4MB * 50) / 100 * sizeof(a ppointer)
// Desired survivor size > 1MB
// Desired survivor size < 16MB
// GC 后 (eden(32MB) 0MB, from survivor(4MB) 1MB, to survivor(4MB) 0MB, tenure gen(40MB) 16MB)
allocation3 = new byte[16 * _1MB]; // (eden(32MB) allocation3 = 16MB, from survivor(4MB) 1MB, to survivor(4MB) 0MB, tenure gen(40MB) 16MB)
allocation3 = null;
// 16MB + 16MB + other > eden(32MB) 触发Minor GC
// 16MB 没有引用 GC
// Desired survivor size < 16MB
// from survivor(4MB) 1MB age = 1 < 15
// GC 后 (eden(32MB) allocation3 = 16MB, from survivor(4MB) 1MB, to survivor(4MB) 0MB, tenure gen(40MB) 16MB)
allocation3 = new byte[16 * _1MB]; // (eden(32MB) allocation3 = 16MB, from survivor(4MB) 1MB, to survivor(4MB) 0MB, tenure gen(40MB) 16MB)
}
}
GC log :
localhost:_003 liangmy$ java -version
java version "1.8.0_151"
Java(TM) SE Runtime Environment (build 1.8.0_151-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.151-b12, mixed mode)
localhost:_003 liangmy$ java -Xms80M -Xmx80M -Xmn40M -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15 -verbose:gc -XX:+PrintGCDetails -XX:+PrintTenuringDistribution -XX:+UseSerialGC lime.jvm._003._006._003.TestTenuringThreshold
[GC (Allocation Failure) [DefNew
Desired survivor size 2097152 bytes, new threshold 15 (max 15)
- age 1: 1333632 bytes, 1333632 total
: 18718K->1302K(36864K), 0.0176838 secs] 18718K->17686K(77824K), 0.0177439 secs] [Times: user=0.01 sys=0.01, real=0.02 secs]
[GC (Allocation Failure) [DefNew
Desired survivor size 2097152 bytes, new threshold 15 (max 15)
- age 2: 1331824 bytes, 1331824 total
: 17686K->1300K(36864K), 0.0015781 secs] 34070K->17684K(77824K), 0.0015996 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
def new generation total 36864K, used 18668K [0x00000007bb000000, 0x00000007bd800000, 0x00000007bd800000)
eden space 32768K, 53% used [0x00000007bb000000, 0x00000007bc0f5dc8, 0x00000007bd000000)
from space 4096K, 31% used [0x00000007bd000000, 0x00000007bd145270, 0x00000007bd400000)
to space 4096K, 0% used [0x00000007bd400000, 0x00000007bd400000, 0x00000007bd800000)
tenured generation total 40960K, used 16384K [0x00000007bd800000, 0x00000007c0000000, 0x00000007c0000000)
the space 40960K, 40% used [0x00000007bd800000, 0x00000007be800010, 0x00000007be800200, 0x00000007c0000000)
Metaspace used 2646K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 286K, capacity 386K, committed 512K, reserved 1048576K
2* 减小allocation1的空间
package lime.jvm._003._006._003;
/**
* @Author : Liangmy
* @Description :
* @Date : Created in 2020/1/5 下午12:56
* @Modified By :
* <p>
* 代码清代3-7 长期存活的对象进入老年代
* <p>
* VM Args : -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=1 -verbose:gc -XX:+PrintGCDetails -XX:+PrintTenuringDistribution -XX:+UseSerialGC
* java -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=1 -verbose:gc -XX:+PrintGCDetails -XX:+PrintTenuringDistribution -XX:+UseSerialGC lime.jvm._003._006._003.TestTenuringThreshold
* java -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15 -verbose:gc -XX:+PrintGCDetails -XX:+PrintTenuringDistribution -XX:+UseSerialGC lime.jvm._003._006._003.TestTenuringThreshold
* 1* 提升Survivor空间 : java -Xms80M -Xmx80M -Xmn40M -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15 -verbose:gc -XX:+PrintGCDetails -XX:+PrintTenuringDistribution -XX:+UseSerialGC lime.jvm._003._006._003.TestTenuringThreshold
* 1* 减小allocation1的空间 : java -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15 -verbose:gc -XX:+PrintGCDetails -XX:+PrintTenuringDistribution -XX:+UseSerialGC lime.jvm._003._006._003.TestTenuringThreshold
*
* <p>
*/
public class TestTenuringThreshold {
private static final int _1MB = 1024 * 1024;
public static void main(String[] args) {
testTenuringThreshold();
}
private static void testTenuringThreshold() {
byte[] allocation1, allocation2, allocation3;
allocation1 = new byte[_1MB / 8]; // (eden(8MB) allocation1 = 0.125MB, from survivor(1MB) 0MB, to survivor(1MB) 0MB, tenure gen(10MB) 0MB)
// 什么时候进入老年代取决于XX:MaxTenuringThreshold设置
allocation2 = new byte[4 * _1MB]; // (eden(8MB) allocation1 = 0.125MB, allocation2 = 4MB,from survivor(1MB) 0MB, to survivor(1MB) 0MB, tenure gen(10MB) 0MB)
// 0.125MB + 4MB + 4MB = 8.125MB > eden(8MB) 触发Minor GC
// Desired survivor size = (from survivor(1MB) * TargetSurvivorRatio) / 100 * sizeof(a pointer) = (1MB * 50) / 100 * sizeof(a ppointer)
// Desired survivor size > 0.125MB
// Desired survivor size < 4MB
// GC 后 (eden(8MB) 0MB, from survivor(1MB) 0.125MB, to survivor(1MB) 0MB, tenure gen(10MB) 4MB)
allocation3 = new byte[4 * _1MB]; // (eden(8MB) allocation3 = 4MB, from survivor(1MB) 0.125MB, to survivor(1MB) 0MB, tenure gen(10MB) 4MB)
allocation3 = null;
// 4MB + 4MB + other > eden(8MB) 触发Minor GC
// 4MB 没有引用 GC
// Desired survivor size < 4MB
// from survivor(1MB) 0.125MB age = 1 < 15
// GC 后 (eden(8MB) allocation3 = 4MB, from survivor(1MB) 0.125MB, to survivor(1MB) 0MB, tenure gen(10MB) 4MB)
allocation3 = new byte[4 * _1MB]; // (eden(8MB) allocation3 = 4MB, from survivor(1MB) 0.125MB, to survivor(1MB) 0MB, tenure gen(10MB) 4MB)
}
}
GC log :
localhost:_003 liangmy$ java -version
java version "1.8.0_151"
Java(TM) SE Runtime Environment (build 1.8.0_151-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.151-b12, mixed mode)
localhost:_003 liangmy$ java -Xms20M -Xmx20M -Xmn10M -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15 -verbose:gc -XX:+PrintGCDetails -XX:+PrintTenuringDistribution -XX:+UseSerialGC lime.jvm._003._006._003.TestTenuringThreshold
[GC (Allocation Failure) [DefNew
Desired survivor size 524288 bytes, new threshold 15 (max 15)
- age 1: 416128 bytes, 416128 total
: 4895K->406K(9216K), 0.0067625 secs] 4895K->4502K(19456K), 0.0068236 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [DefNew
Desired survivor size 524288 bytes, new threshold 15 (max 15)
- age 2: 414320 bytes, 414320 total
: 4502K->404K(9216K), 0.0011564 secs] 8598K->4500K(19456K), 0.0011933 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
Heap
def new generation total 9216K, used 4746K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
eden space 8192K, 53% used [0x00000007bec00000, 0x00000007bf03d8a0, 0x00000007bf400000)
from space 1024K, 39% used [0x00000007bf400000, 0x00000007bf465270, 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 2646K, capacity 4486K, committed 4864K, reserved 1056768K
class space used 286K, capacity 386K, committed 512K, reserved 1048576K
3.6.4 动态对象年龄判定 :如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。