文章目录
- 基本概念
- 垃圾回收类型
- 垃圾回收算法
- 垃圾回收器
- VM参数设置
- 控制vm参数
- 内存参数
- GC参数
- GC实例分析
基本概念
垃圾回收类型
- Minor GC:对新生代进行的垃圾回收,所以也叫Young GC
- Major GC:对老年代进行的垃圾回收,所以也叫Old GC
- Full GC:对整个Java堆和元空间进行的垃圾回收
- Mixed GC:混合GC,新生代、老年代进行垃圾回收,目前只有G1支持
Young = Eden + Survivor0 + Survivor1
垃圾回收算法
- 标记-清除算法:会产生内存碎片
- 标记-复制算法:大量对象复制开销大,需要预留额外的内存空间用来做复制的目标空间(如果要保证空间足够至少要50%(1/2),但实际不会预留那么多,所以需要空间担保,例如默认的Eden:Survivor=8:1,2个S区就只有1/10)
- 标记-整理算法:整理算法是复制算法的进阶版,将内存一端复制到另一端,这样不需要浪费1/2的空间,也不用空间担保,但是整个内存的地址引用都可能变化,所以必须STW(复制算法要么有1/2空间浪费,要么需要空间担保,Old显然接受不了1/2的空间浪费,也不想Eden有Old空间做空间担保,所以只能使用整理算法)
垃圾回收器
- Serial:新生代,标记复制算法,-XX:+UseSerialGC
- . Parallel Scavenge:新生代,使用标记复制算法,-XX:+UseParallelGC,-XX:MaxGCPauseMillis(大于0,最大停顿),-XX:GCTimeRatio(垃圾回收时间占比,[0,100],默认19,表示垃圾回收时间不超过1/1+19=1/20=5%)
- ParNew,新生代,标记复制算法,-XX:+UseParNewGC,配合CMS使用
- Serial Old,老年代,标记整理算法,-XX:+UseSerialGC
- Parallel Old,老年代,标记整理算法,-XX:+UseParallelOldGC,-XX:ParallelGCThreads=n(设置回收器线程数量)
- CMS,老年代
- G1,整个堆
JDK9已经默认使用G1了,所以这里再重点介绍一下:
- G1将整个Java堆划分成多个大小不等的Region
- 每个Region可以根据需要扮演Eden区、Survivor区、或者Old
G1回收阶段1:
- Eden内存不足,触发新生代GC开始回收Eden区和Survivor区
- GC后,Eden区会被清空,Survivor区至少会保留一个,其余的对象要么被清理,要么被晋升到老年代
这次回收,新生代的大小会根据实际运行情况调整
G1回收阶段2:并发标记周期
- 初始标记:STW,仅标记GC Roots直接关联的对象,会伴随一次新生代GC,非常快
- S0扫描:非STW,扫描初始标记存活的Survivor区直接可达的老年代区域,并标记
- 并发标记:非STW,扫描并查找整个堆内存活的对象并标记
- 重新标记:STW,修正并发标记期间因为用户线程继续执行而导致对象间的引用被改变
- 独占清理:STW,计算各个Region的回收价值,对Region进行排序,识别可供混合回收的区域
- 并发清理:识别并清理完全空闲的Region,不会造成停顿
G1回收阶段3:混合回收
阶段2回收了部分空间,但是比例还是相当低的,不过G1已经知道各个Region的回收情况了。
因此可以优先回收垃圾最多的Region,这些Region既包含了新生代,也包含了老年代
被清理的Region内的存活对象会被移动到其他Region,避免内存碎片。
VM参数设置
控制vm参数
参数 | 说明 |
---|---|
-XX:+PrintFlagsInitial | 打印vm默认选项 |
-XX:+PrintFlagsFinal | 打印应用运行时生效的选项 |
-XX:+PrintCommandLineflags | 打印启动命令手动设置的vm参数 |
-XX:+PrintVMOptions | 打印vm选项 |
内存参数
参数 | 说明 |
---|---|
-Xss128k或-XX:ThreadStackSize=128k | 设置每个线程栈大小 |
-Xms2048m或-XX:InitialHeapSize=2048m | 设置JVM初始堆内存大小 |
-Xmx2048m或-XX:MaxHeapSize=2048m | 设置JVM最大堆内存大小 |
-Xmn1g | 设置年轻代大小 |
-XX:NewSize=1024m | 设置年轻代初始值 |
-XX:MaxNewSize=1024m | 设置年轻代最大值 |
-XX:SurvivorRatio=8 | 设置年轻代中的Eden区与Survivor去的比值,默认为8 |
-XX:NewRatio=4 | 设置老年代与年轻代(包括1个Eden和2个Survivor区)的比值 |
-XX:PretenureSizeThreadshold=1024 | 设置让大于此阈值的对象直接分配到老年代,单位为字节,只对Serial,ParNew收集器有效 |
-XX:MaxTenuringThreshold=15 | 新生代每次MinorGC之后,还存活的对象年龄加1,当对象年龄大于设置的值时就进入老年代 |
-XX:+PrintTenuringDistribution | 每次MinorGC之后打印出当前的使用Survivor对象的年龄分布 |
-XX:TargetSurvivorRatio | MinorGC结束后Survivor区域中占用空间期望比例 |
-XX:PermSize=256m | 设置永久代初始值大小 |
-XX:MaxPermSize=256m | 设置永久代最大值大小 |
-XX:MetaspaceSize | 元空间初始大小 |
-XX:MaxMetaspaceSize | 元空间最大值 |
-XX:+HeapDumpOnOutOfMemoryError | 内存溢出时dump内存文件 |
-XX:HeapDumpPath=/path/to/dump | 内存溢出时dump内存文件路径 |
注意:
- SurvivorRatio是年轻代Eden/Survivor的比值,例如默认8,就表示Eden/Survivor=8,因为有2个Survivor所以每个Survivor占1/(8+1+1)=1/10
- NewRatio是老年代与年轻代的比值,默认值2=Old/(Eden+S0+S1)=2:1,如果-Xmn1g,那么Old就是2G
- JDK8及其以上版本永久代(PermSize、MaxPermSize)已经被元空间(MetaspaceSize、MaxMetaspaceSize)取代
GC参数
参数 | 说明 |
---|---|
-XX:+PrintGCDetails | 打印详细日志 |
-XX:+PrintGCTimeStamps | 打印GC时jvm启动时间 |
-XX:+PrintGCDateStamps | 打印GC日志时间 |
-XX:+DisableExplicitGC | 在程序代码中不允许调用System.gc() |
-Xloggc:F:/tmp/gc.log | 将GC日志输出到指定文件 |
-XX:+PrintHeapAtGC | 每次GC时都打印堆信息 |
-XX:+PrintGC | 打印GC的简要信息,高版本使用-Xlog:gc |
-verbose:gc | 等价-XX:+PrintGC,标准的选项 |
GC实例分析
我们可以通过下面的代码和vm参数来简单看一下垃圾回收。
import java.util.ArrayDeque;
import java.util.Queue;
public class Start {
private static final Queue<byte[]> queue = new ArrayDeque<>();
/**
* -Xms1g -Xmx1g -XX:+PrintGC
*
* @param args
*/
public static void main(String[] args) {
mem();
}
public static void mem() {
while (true) {
byte[] kb = new byte[1024 * 1024];
if (queue.size() >= 100) {
queue.poll();
}
queue.offer(kb);
}
}
}
不同的垃圾回收器日志不一样,但是内存的关键信息大差不差: