深入理解java虚拟机是一本非常经典的书籍,每次阅读总能看到新的东西。当然最重要的是实践。在平时个工作过程中,我们很少有机会去调jvm启动参数所以可能对java垃圾回收的实践还是缺乏不少。理论很明白,没有实践操作还是不行的,等到线上有问题不可能直接跑到线上调试。所以平时自己写一些拿来调试分析的案例还是有必要。
以下是书中的几段例子。
1.java虚拟机垃圾回收使用的不是引用计数算法,故可以正确回收互相引用的对象
package com.jvm.garbage;
import org.junit.Test;
/**
* 互相引用的对象也会被垃圾回收器回收 -XX:+UseSerialGC -XX:+PrintGCDetails
*
* @author lcq
*
*/
public class ReferenceCountGC {
public Object instance = null;
private static final int _1MB = 1024 * 1024;
private byte[] bigSize = new byte[2 * _1MB];
@Test
public void testGC() {
ReferenceCountGC objA = new ReferenceCountGC();
ReferenceCountGC objB = new ReferenceCountGC();
objA.instance = objB;
objB.instance = objA;
objA = null;
objB = null;
System.gc();
}
}
打印输出:
[GC [DefNew: 2717K->410K(4928K), 0.0024818 secs] 2717K->410K(15872K), 0.0025018 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC [DefNew: 4591K->0K(4928K), 0.0026068 secs] 4591K->4505K(15872K), 0.0026366 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (System) [Tenured: 4505K->2457K(10944K), 0.0092492 secs] 6553K->2457K(15872K), [Perm : 1496K->1496K(12288K)], 0.0092814 secs] [Times: user=0.01 sys=0.00, real=0.02 secs]
Heap
def new generation total 4992K, used 102K [0x241b0000, 0x24710000, 0x29700000)
eden space 4480K, 2% used [0x241b0000, 0x241c9a40, 0x24610000)
from space 512K, 0% used [0x24610000, 0x24610000, 0x24690000)
to space 512K, 0% used [0x24690000, 0x24690000, 0x24710000)
tenured generation total 10944K, used 2457K [0x29700000, 0x2a1b0000, 0x341b0000)
the space 10944K, 22% used [0x29700000, 0x299664d0, 0x29966600, 0x2a1b0000)
compacting perm gen total 12288K, used 1499K [0x341b0000, 0x34db0000, 0x381b0000)
the space 12288K, 12% used [0x341b0000, 0x34326f10, 0x34327000, 0x34db0000)
ro space 10240K, 54% used [0x381b0000, 0x3872c510, 0x3872c600, 0x38bb0000)
rw space 12288K, 55% used [0x38bb0000, 0x3924fb78, 0x3924fc00, 0x397b0000)
从结果可以看出可以正常回收对象。
2.内存分配和垃圾回收策略
package com.jvm.garbage;
import org.junit.Test;
/**
* 垃圾回收 内存分配情况 -XX:+UseSerialGC -Xms20M -Xmx20m -Xmn10m -XX:+PrintGCDetails -XX:SurvivorRatio=8
* XX:SurvivorRatio表示eden:survivor=8:1
*
*
* @author lcq
*
*/
public class TestAllocation {
private static final int _1MB = 1024 * 1024;
@Test
public 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];
}
}
打印输出:
[GC [DefNew: 6945K->410K(9216K), 0.0042955 secs] 6945K->4506K(19456K), 0.0043188 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
Heap
def new generation total 9216K, used 6751K [0x32db0000, 0x337b0000, 0x337b0000)
eden space 8192K, 77% used [0x32db0000, 0x333e1300, 0x335b0000)
from space 1024K, 40% used [0x336b0000, 0x33716af0, 0x337b0000)
to space 1024K, 0% used [0x335b0000, 0x335b0000, 0x336b0000)
tenured generation total 10240K, used 4096K [0x337b0000, 0x341b0000, 0x341b0000)
the space 10240K, 40% used [0x337b0000, 0x33bb0020, 0x33bb0200, 0x341b0000)
compacting perm gen total 12288K, used 1499K [0x341b0000, 0x34db0000, 0x381b0000)
the space 12288K, 12% used [0x341b0000, 0x34326e30, 0x34327000, 0x34db0000)
ro space 10240K, 54% used [0x381b0000, 0x3872c510, 0x3872c600, 0x38bb0000)
rw space 12288K, 55% used [0x38bb0000, 0x3924fb78, 0x3924fc00, 0x397b0000)
年轻代大小eden为8M,allocation4在分配时survivor只有1M,Eden已经占用6M,所以此时只能分配到老年代。
3.长期存活的对象进入老年代
package com.jvm.garbage;
import org.junit.Test;
/**
* 垃圾回收 内存分配情况 -XX:+UseSerialGC -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8
* -XX:MaxTenuringThreshold=1 -XX:+PrintTenuringDistribution
*
* @author lcq
*
*/
public class TestTenuringThreshold {
private static final int _1MB = 1024 * 1024;
@Test
public void testTenuringThreshold() {
System.gc();
byte[] allocation1, allocation2, allocation3;
allocation1 = new byte[_1MB / 4];
allocation2 = new byte[4 * _1MB];
allocation3 = new byte[4 * _1MB];
allocation3 = null;
allocation3 = new byte[4 * _1MB];
}
}
MaxTenuringThreshold设置为1
[Full GC (System) [Tenured: 0K->410K(10240K), 0.0102130 secs] 2849K->410K(19456K), [Perm : 1495K->1495K(12288K)], 0.0102419 secs] [Times: user=0.02 sys=0.00, real=0.02 secs]
[GC [DefNew
Desired survivor size 524288 bytes, new threshold 1 (max 1)
- age 1: 262160 bytes, 262160 total
: 4515K->256K(9216K), 0.0026166 secs] 4926K->4762K(19456K), 0.0026376 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC [DefNew
Desired survivor size 524288 bytes, new threshold 1 (max 1)
: 4352K->0K(9216K), 0.0002393 secs] 8858K->4762K(19456K), 0.0002552 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
def new generation total 9216K, used 4344K [0x32db0000, 0x337b0000, 0x337b0000)
eden space 8192K, 53% used [0x32db0000, 0x331ee330, 0x335b0000)
from space 1024K, 0% used [0x335b0000, 0x335b0000, 0x336b0000)
to space 1024K, 0% used [0x336b0000, 0x336b0000, 0x337b0000)
tenured generation total 10240K, used 4762K [0x337b0000, 0x341b0000, 0x341b0000)
the space 10240K, 46% used [0x337b0000, 0x33c56b80, 0x33c56c00, 0x341b0000)
compacting perm gen total 12288K, used 1499K [0x341b0000, 0x34db0000, 0x381b0000)
the space 12288K, 12% used [0x341b0000, 0x34326e60, 0x34327000, 0x34db0000)
ro space 10240K, 54% used [0x381b0000, 0x3872c510, 0x3872c600, 0x38bb0000)
rw space 12288K, 55% used [0x38bb0000, 0x3924fb78, 0x3924fc00, 0x397b0000)
MaxTenuringThreshold参数是设置survivor中对象晋升老年代的阀值。从结果可以看出age超过1后会被分配到老年代。
MaxTenuringThreshold设置为15
[Full GC (System) [Tenured: 0K->410K(10240K), 0.0106688 secs] 2849K->410K(19456K), [Perm : 1495K->1495K(12288K)], 0.0106981 secs] [Times: user=0.02 sys=0.00, real=0.02 secs]
[GC [DefNew
Desired survivor size 524288 bytes, new threshold 15 (max 15)
- age 1: 262160 bytes, 262160 total
: 4515K->256K(9216K), 0.0026352 secs] 4926K->4762K(19456K), 0.0026572 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC [DefNew
Desired survivor size 524288 bytes, new threshold 15 (max 15)
- age 2: 262160 bytes, 262160 total
: 4352K->256K(9216K), 0.0002356 secs] 8858K->4762K(19456K), 0.0002519 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
def new generation total 9216K, used 4600K [0x32db0000, 0x337b0000, 0x337b0000)
eden space 8192K, 53% used [0x32db0000, 0x331ee330, 0x335b0000)
from space 1024K, 25% used [0x335b0000, 0x335f0010, 0x336b0000)
to space 1024K, 0% used [0x336b0000, 0x336b0000, 0x337b0000)
tenured generation total 10240K, used 4506K [0x337b0000, 0x341b0000, 0x341b0000)
the space 10240K, 44% used [0x337b0000, 0x33c16b70, 0x33c16c00, 0x341b0000)
compacting perm gen total 12288K, used 1499K [0x341b0000, 0x34db0000, 0x381b0000)
the space 12288K, 12% used [0x341b0000, 0x34326e60, 0x34327000, 0x34db0000)
ro space 10240K, 54% used [0x381b0000, 0x3872c510, 0x3872c600, 0x38bb0000)
rw space 12288K, 55% used [0x38bb0000, 0x3924fb78, 0x3924fc00, 0x397b0000)
晋升阀值设置为15后,对象会保持在survivor区中。
在测试这个例子时,开始没有添加System.gc();这句代码一直不能测试出结果,每次对象一开始都会分配到老年代。后来怀疑是jvm启动的时候相同年龄的对象比较多,而这些对象总大小之和超过了survivor区大小的一半,会强制移到老年代,而不去比较MaxTenuringThreshold阀值。所以加上System.gc();进行一下full gc之后再创建对象进行测试。