1. 概述
HotSpot
虚拟机中多数收集器都采用了分代收集来管理堆内存,那内存回收时就必须能决策哪些存活对象应当放在新生代,哪些存活对象放在老年代中。
为做到这点,虚拟机给每个对象定义了一个对象年龄(Age)计数器
,存储在对象头中(详见第2章)。对象通常在Eden
区里诞生,如果经过第一次Minor GC
后仍然存活,并且能被Survivor
容纳的话,该对象会被移动到Survivor
空间中,并且将其对象年龄设为1岁。对象在Survivor区中每熬过一次Minor GC
,年龄就增加1岁,当它的年龄增加到一定程度(默认为15),就会被晋升到老年代中。
对象晋升老年代的年龄阈值,可以通过参数-XX: MaxTenuringThreshold
设置。
2. 例子
读者可以试试分别以-XX:MaxTenuringThreshold=1
和-XX:MaxTenuringThreshold=15
两种设置来执行代码清单3-9中的testTenuringThreshold()
方法,此方法中allocation1对象需要256KB内存,Survivor空间可以容纳。当-XX:MaxTenuringThreshold=1
时,allocation1对象在第二次GC发生时进入老年代,新生代已使用的内存在垃圾收集以后非常干净地变成0KB。而当-XX:MaxTenuringThreshold=15
时,第二次GC发生后,allocation1对象则还留在新生代Survivor空间,这时候新生代仍然有404KB被占用。
private static final int _1MB = 1024 * 1024;
/**
* VM参数:-verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails
* -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=1
* -XX:+PrintTenuringDistribution -XX:+UseSerialGC
*/
public static void testTenuringThreshold() {
byte[] allocation1, allocation2, allocation3;
//内存 A
allocation1 = new byte[_1MB / 4]; // 什么时候进入老年代决定于XX:MaxTenuringThreshold设置
//内存 B
allocation2 = new byte[4 * _1MB];
//内存 C
allocation3 = new byte[4 * _1MB];
allocation3 = null;
//内存 D
allocation3 = new byte[4 * _1MB];
}
public static void main(String[] args) {
testTenuringThreshold();
}
-Xms20M 初始jvm内存
-Xmx20M 最大jvm内存
-Xmn10M 最大堆内存10M(新生代可用最大空间)
-XX:SurvivorRatio=8 表示Eden区:Survivor = 8:1:1 新生代总共10M,Eden区占8M,2个Survivor各1M
2.1 以-XX:MaxTenuringThreshold=1
参数来运行
JDK1.6环境执行
结果:
[`GC` [DefNew
Desired survivor size 524288 bytes, new threshold 1 (`max 1`)
- `age 1`: 413040 bytes, 413040 total
: 4695K->403K(9216K), 0.0078761 secs] 4695K->4499K(19456K), 0.0079214 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[`GC` [DefNew
Desired survivor size 524288 bytes, new threshold 1 (max 1)
: 4499K->0K(9216K), 0.0013783 secs] 8595K->4499K(19456K), 0.0014185 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
def new generation total 9216K, used 4259K [0x32360000, 0x32d60000, 0x32d60000)
eden space 8192K, 52% used [0x32360000, 0x32788fe0, 0x32b60000) `Eden区4M,存储的是内存D`
`from space 1024K, 0% used` [0x32b60000, 0x32b60000, 0x32c60000) `survivor 存储占用0,都移动到老年代`
to space 1024K, 0% used [0x32 c60000, 0x32c60000, 0x32d60000)
tenured generation total 10240K, used 4499K [0x32d60000, 0x33760000, 0x33760000)
`the space 10240K, 43% used` [0x32d60000, 0x331c4d80, 0x331c4e00, 0x33760000) `老年代存储(B:4M+ A:256K+148K)`
compacting perm gen total 12288K, used 379K [0x33760000, 0x34360000, 0x37760000)
the space 12288K, 3% used [0x33760000, 0x337bed80, 0x337bee00, 0x34360000)
ro space 10240K, 51% used [0x37760000, 0x37c925d0, 0x37c92600, 0x38160000)
rw space 12288K, 55% used [0x38160000, 0x387fd978, 0x387fda00, 0x38d60000)
红色标记,显示经过2次gc后,新生代的Survivor为0%,说明移入到了老年代
详细分析:
- 执行main函数
main线程占用148K,进入Eden - allocation1 = new byte[_1MB / 4];
新生代尝试申请256K内存A,成功,进入Eden区 - allocation2 = new byte[4 * _1MB];
新生代尝试申请4M内存B,成功,进入Eden区,此时Eden区占用 (4M+256K+148K) - allocation3 = new byte[4 * _1MB];
新生代尝试申请4M内存C,由于Eden区总大小8M,已经占用(4M+256K+148K)发现不够用了,触发GC:- A进入Survivor,因为256K<1M ,此时Survivor占用(256K+148K)
- B进入老年代,因为4M>1M,Survivor放不下,此时老年代(4M)
- C进入Eden区,此时Eden区占用 (4M)
- allocation3 = null;
让Eden区的内存C符合GC条件,可以注释掉,不影响本例的目的 - allocation3 = new byte[4 * _1MB];
尝试在新生代Eden区申请4M内存D,发现内存刚好到达8M,几乎满了,触发GC- 内存C直接被清除掉,此时Eden区使用0
- Survivor占用(256K+148K)生命周期+1后>1,符合晋升老年代条件,此时老年代(4M+256K+148K),Survivor占用0
- 在Eden区申请4M,存储内存D
有关gc日志的补充解释,新生代总的初始可用空间total=eden+from=8M+1M=9M:
内存的划分可以参见《JVM老年代(Old)和新生代(年轻代、Young)的比例》
2.2 以-XX:MaxTenuringThreshold=15参数来运行
多次执行的结果和书上一致
[`GC `[DefNew
Desired survivor size 524288 bytes, new threshold 15 (`max 15`)
- `age 1`: 413040 bytes, 413040 total
: 4695K->403K(9216K), 0.0045132 secs] 4695K->4499K(19456K), 0.0045568 secs] [Times: user=0.02 sys=0.00, real=0.00 secs]
[`GC` [DefNew
Desired survivor size 524288 bytes, new threshold 15 (max 15)
- `age 2`: 413040 bytes, 413040 total
: 4499K->403K(9216K), 0.0009964 secs] 8595K->4499K(19456K), 0.0010297 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
def new generation total 9216K, used 4827K [0x32360000, 0x32d60000, 0x32d60000)
eden space 8192K, 54% used [0x32360000, 0x327b1fa8, 0x32b60000) `eden存储D:4M`
from space 1024K, `39% used` [0x32b60000, 0x32bc4d70, 0x32c60000) `// Survivor存储A:256K+148K`
to space 1024K, 0% used [0x32c60000, 0x32c60000, 0x32d60000)
tenured generation total 10240K, used 4096K [0x32d60000, 0x33760000, 0x33760000)
the space 10240K, `40% used` [0x32d60000, 0x33160010, 0x33160200, 0x33760000) `//老年代存储B:4M`
compacting perm gen total 12288K, used 379K [0x33760000, 0x34360000, 0x37760000)
the space 12288K, 3% used [0x33760000, 0x337bed80, 0x337bee00, 0x34360000)
ro space 10240K, 51% used [0x37760000, 0x37c925d0, 0x37c92600, 0x38160000)
rw space 12288K, 55% used [0x38160000, 0x387fd978, 0x387fda00, 0x38d60000)
从结果上能看到,经过2次gc后,Survivor仍存储A:256K+148K`,说明没有直接进入老年代,需要等到15次才会进去。
注意:重启eclipse执行第一次结果会多一行高亮显示的:
[GC [DefNew
Desired survivor size 524288 bytes, new threshold 15 (max 15)
- age 1: 413040 bytes, 413040 total
: 4695K->403K(9216K), 0.0049113 secs] 4695K->4499K(19456K), 0.0049442 secs] [Times: user=0.02 sys=0.00, real=0.00 secs]
[GC [DefNew
Desired survivor size 524288 bytes, new threshold 15 (max 15)
`- age 1: 136 bytes, 136 total` `//多出一行`
- age 2: 412832 bytes, 412968 total
: 4663K->403K(9216K), 0.0010729 secs] 8759K->4499K(19456K), 0.0011123 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
def new generation total 9216K, used 4663K [0x32360000, 0x32d60000, 0x32d60000)
eden space 8192K, 52% used [0x32360000, 0x32788fe0, 0x32b60000)
from space 1024K, 39% used [0x32b60000, 0x32bc4d28, 0x32c60000)
to space 1024K, 0% used [0x32c60000, 0x32c60000, 0x32d60000)
tenured generation total 10240K, used 4096K [0x32d60000, 0x33760000, 0x33760000)
the space 10240K, 40% used [0x32d60000, 0x33160010, 0x33160200, 0x33760000)
compacting perm gen total 12288K, used 379K [0x33760000, 0x34360000, 0x37760000)
the space 12288K, 3% used [0x33760000, 0x337bed80, 0x337bee00, 0x34360000)
ro space 10240K, 51% used [0x37760000, 0x37c925d0, 0x37c92600, 0x38160000)
rw space 12288K, 55% used [0x38160000, 0x387fd978, 0x387fda00, 0x38d60000)