回收期演变及概览
前面我们讲了很多 垃圾收集的原理和知识点,下面我们针对各种垃圾收集器进行JVM调优,JVM调优其实都是根据对应的垃圾收集器特性而去做调整和优化。
不同垃圾收集器的产生总体可以划分为几个阶段。。
- 第一阶段:单线程收集时代(Serial和Serial Old)
- 第二阶段:多线程收集时代(Parallel Scanvenge 和Parallel Old)
- 第三阶段:并发收集时代(ParNew和CMS)
- 第四阶段:通用并发收集时代(G1)
实际项目中并不会设置唯一的收集器,有可能是几种混用,比如ParNew其实和Paralled Sacanvenge差不多,但是就因为ParNew是唯一的可以和CMS配合进行新生代收集的收集器,所以它就经常和CMS一起使用
- 新生代收集器: Serial、ParNew、Parallel Scavenge;
- 老年代收集器: Serial Old、CMS、Parallel Old;
- 通用收集器: G1;
收集器常用组合:
- Serial + Serial Old
- Parallel Scavenge + Parallel Old
- ParNew + CMS配合
- G1(不需要组合其他收集器)
下面我们会一个一个分析 如何使用及如何调优,对于 Serial + Serial Old 这个单线程垃圾收集器几乎不会再实际项目中使用,我们简单了解一下即可
1.Serial/Serial Old
- Serial 年轻代,单线程收集器,最基本,历史最悠久的垃圾收集器
- Serial Old 老年代, 单线程收集器
为啥它没人用?
- 因为他是单线程,“单线程” 的意义不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾收集工作
- 更重要的是它在进行垃圾收集工作的时候必须暂停其他所有的工作线程( “Stop The World” ),直到它收集结束。
Serial收集器没有线程交互的开销,可以获得很高的单线程收集效率。但是屁用没有,没有人实战中会开一个单线程专门收集垃圾而且还能忍受StopTheWorld
2.测试Serial/Serial Old 垃圾收集器
查看你自己的jdk使用的默认垃圾收集器,如果你是java8,而且没有在项目中指定垃圾收集器,那么他就是ParallelGC 并行垃圾收集器
#查看jdk版本 使用的默认垃圾收集器
java -XX:+PrintCommandLineFlags -version
打印日志
C:\Users\jzj>java -XX:+PrintCommandLineFlags -version
-XX:InitialHeapSize=265507840 -XX:MaxHeapSize=4248125440 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
java version "1.8.0_181"
Java(TM) SE Runtime Environment (build 1.8.0_181-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.181-b13, mixed mode)
可以看到我的jdk默认的是 UseParallelGC
我们现在设置单线程收集器Serial/SerialOld
2.1 设置JVM参数 测试GC日志
设置JVM参数
-verbose:gc -XX:+UseSerialGC -Xmx30M -XX:+PrintGC -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError
单线程 Serial/SerialOld def new generation
- 年轻代 def new generation
- 伊甸区 eden space
- Surviral from space区 from
- Surviral to space区 to
- 老年代 tenured generation
- 元空间 Metaspace
2.2 JVM测试类
Jvm测试类,设置JVM最大堆30M, MyBigObj对象 每个5M, 创建3个对象, 看下GC内容
@Slf4j
public class JvmTest {
static class MyBigObj {
// 创建5M大小的字节数组
private byte[] bytes = new byte[1024*1024*5];
}
public static void main(String[] args) throws Exception {
//创建一个大对象
MyBigObj big1 = new MyBigObj();
MyBigObj big2 = new MyBigObj();
MyBigObj big3 = new MyBigObj();
System.gc();
Thread.sleep(5000);
}
}
打印GC日志
[GC (Allocation Failure) [DefNew: 7056K->1024K(9216K), 0.0025395 secs] 7056K->1466K(29696K), 0.0025691 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [DefNew: 6144K->0K(9216K), 0.0055420 secs] 6586K->6586K(29696K), 0.0055680 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[Full GC (System.gc()) [Tenured: 6586K->11706K(20480K), 0.0063238 secs] 11706K->11706K(29696K), [Metaspace: 4672K->4672K(1056768K)], 0.0063757 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
Heap
def new generation total 9216K, used 731K [0x00000007be200000, 0x00000007bec00000, 0x00000007bec00000)
eden space 8192K, 8% used [0x00000007be200000, 0x00000007be2b6c30, 0x00000007bea00000)
from space 1024K, 0% used [0x00000007bea00000, 0x00000007bea00000, 0x00000007beb00000)
to space 1024K, 0% used [0x00000007beb00000, 0x00000007beb00000, 0x00000007bec00000)
tenured generation total 20480K, used 11706K [0x00000007bec00000, 0x00000007c0000000, 0x00000007c0000000)
the space 20480K, 57% used [0x00000007bec00000, 0x00000007bf76e8c0, 0x00000007bf76ea00, 0x00000007c0000000)
Metaspace used 4782K, capacity 5064K, committed 5248K, reserved 1056768K
class space used 530K, capacity 564K, committed 640K, reserved 1048576K
堆一共JVM设置 30M
- 年轻/老年 1:2, 所以年轻代 大概 10M , 老年代大概20M
- 年轻代中区分 eden:from:to, 8:1:1, 所以年轻代可用 大概9M左右
- def new generation total 9216K =9M 年轻代可用
- eden space 8192K, eden区 8M
- from space 1024K, from 1M
- to space 1024K, to 1M
- tenured generation total 20480K=20M 老年代
- Metaspace 元空间 5M左右
3. GC日志分析
GC日志参数 | 含义 |
---|---|
GC (Allocation Failure) | GC引起原因是年轻代没有足够空间存储新数据 |
Full GC (System.gc() | FullGC引起原因是老年代空间不足,gc垃圾回收后依旧存在大量对象,无法回收 |
DefNew | 新生代,这个名称由收集器决定。Serial 收集器的新生代 |
Tenured | Seriale Old 收集器配套的老年代 |
Metaspace | 元空间 Java8 之后的永久代变为元空间 |
#第一行分析
[GC (Allocation Failure) [DefNew: 7056K->1024K(9216K), 0.0025395 secs] 7056K->1466K(29696K), 0.0025691 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
- [GC (Allocation Failure) 发生GC
- [DefNew: 7056K->1024K(9216K), 0.0025395 secs]
- 发生在年轻代, 垃圾回收前 7056K, 垃圾回收后 1024K存留, 年轻代总容量 9216K
- 7056K->1466K(29696K), 0.0025691 secs
- gc前Java堆已使用容量7056K->gc后Java堆已使用容量1466K(Java堆的总容量29696K-已分配), DefNew 新生代回收gc耗时0.0025691 secs
#第二行分析
[Full GC (System.gc()) [Tenured: 6586K->11706K(20480K), 0.0063238 secs] 11706K->11706K(29696K), [Metaspace: 4672K->4672K(1056768K)], 0.0063757 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
- [Full GC (System.gc()) 发生了FullGC, 发生了StopTheWorld 老年代发生GC
- [Tenured: 6586K->11706K(20480K), 0.0063238 secs]
- Tenured 老年代垃圾回收, 垃圾回收前6586K->垃圾回收后11706K, 老年代大小20480K, 回收耗时0.0063238 secs
- 11706K->11706K(29696K)
- gc前Java堆已使用容量11706K->gc后Java堆已使用容量11706K(Java堆的总容量29696K-已分配)
- [Metaspace: 4672K->4672K(1056768K)] 元空间 回收前4672K, 回收后4672K, 总大小1056768K
- 整个FullGC 耗时 0.0063757 secs
这就是 Serial/SerialOld 的垃圾回收机制及打印的GC日志,可以看到 采用Serial/SerialOld 的时候,他们的年轻代和老年代分别用DefNew 和 Tenured来代表, 不同的垃圾收集器采用不同的收集机制