目录
标记-复制
年轻代分为,eden,s0,s1三个区;
新new出来的对象,放在eden区,eden区满后,做young gc,把eden区中的存活对象,复制
到s0区,然后清空eden区;
下一次young gc时,把eden区和s0区中的存活对象,一起复制到s1区,然后清空eden区和s0
区;之后按着上述规则循环操作;
当年轻代的存活对象,经过默认15次(可通过参数修改)的young gc之后,还存活着,就会
晋升到老年代;
标记-清除-整理
遍历并标记所有的可达对象,清除不可达对象所占用的内存,清除垃圾之后的内存空间不连
续,做碎片整理,让内存空间连续起来;
标记存活对象的过程,是通过遍历GC Root对象引用的对象来实现的;
GC Root对象
标记可达对象时,是从GC Root对象出发,然后遍历所有可以达到的对象;GC Root对象:
1,当前正在执行的方法里的局部变量和输入参数相关的对象;
2,活动线程相关的对象;
3,所有类的静态字段相关的对象;
4,Java本地接口引用相关的对象;
安全点检测
GC回收内存时,会触发全线暂停STW;
JVM中的STW(Stop-the-world) 是通过安全点(safepoint)机制来实现的。当虚拟机收到
Stop-the-world请求,它便会等待所有的线程都到达安全点,才允许请求STW的线程进行独占的工
作。
安全点的初始目的并不是让其他线程停下,而是找到一个稳定的执行状态。在这个执行状态
下,JVM的堆栈不会发生变化。垃圾回收器便能够“安全”地执行可达性分析。
对于解释执行来说,字节码与字节码之间皆可作为安全点。当有安全点请求时,执行一条字
节码便进行一次安全点检测。
执行即时编译器生成的机器码则比较复杂。由于这些代码直接运行在底层硬件之上,不受
JVM掌控,因此在生成机器码时,即时编译器需要插入安全点检测,以避免机器码长时间没有安全
点检测的情况。
串行GC
启动参数:-XX:+UseSerialGC
特点:
年轻代使用标记-复制算法,老年代使用标记-清除-整理算法;
两者都是单线程的垃圾收集器,不能进行并行处理,都会触发全线暂停(STW),停止所有
的应用线程。
串行GC不能充分利用多核 CPU。不管有多少 CPU 内核,JVM 在垃圾收集时都只能使用单个
核心。
适用场景:该选项只适合几百 MB 堆内存的 JVM,而且是单核 CPU 时比较有用。
并行GC
启动参数:-XX:+UseParallelGC
特点:
年轻代使用标记-复制算法,老年代使用标记-清除-整理算法;
年轻代和老年代的垃圾回收都会触发 STW 事件;
GC时,多线程回收垃圾,线程数默认位CPU核数,可通过参数调整;
GC 期间,所有CPU内核都在并行清理垃圾,所以总暂停时间更短;
适用场景:适用于多核服务器,适用于对吞吐量有要求的场景,对系统资源的有效使用,能
达到更高的吞吐量:
CMS GC
启动参数:-XX:+UseConcMarkSweepGC
特点:
年轻代使用标记-复制算法,触发STW;
老年代采用标记-清除算法,老年代做垃圾回收时,和业务线程并发执行,从而减少老年代GC
时的STW时间:
1,不对老年代进行整理,而是使用空闲列表(free-lists)来管理内存空间的回收。
2,在 标记-清除阶段的大部分工作和应用线程一起并发执行。
默认情况下做标记-清除算法的并发线程数等于 CPU 核心数的 1/4。
老年代GC的步骤:
1,初始标记,为了标记的准确,短暂的STW;
2,并发标记,GC线程和业务线程并行,并发标记,会有误差;
3,并发预清理,GC线程和业务线程并行,处理步骤2中的标记误差;
4,最终标记,为了标记的准确,短暂的STW;
5,并发清除
6,并发重置
适用场景:多核 CPU,对GC停顿导致的系统延迟要求较高;
G1 GC
启动参数:-XX:+UseG1GC -XX:MaxGCPauseMillis=50
MaxGCPauseMillis=50 指定单次GC导致的STW尽量控制在50ms左右;
特点:
堆不再分成年轻代和老年代,而是划分为多个可以存放对象的小块堆区域;
每个小块,动态的被指定为:Eden 区,Survivor区,Old 区;
Eden 区和 Survivor 区合起来就是年轻代,Old 区拼在一起那就是老年代。
G1 不必每次都去收集整个堆空间,每次只处理一部分内存块。
收集原则是:垃圾最多的小块会被优先收集。每次收集的量,控制在MaxGCPauseMillis参数
指定的时间能收集完成;
重要参数:
-XX:G1NewSizePercent:初始年轻代占整个 Java Heap 的大小,默认值为 5%;
-XX:G1MaxNewSizePercent:最大年轻代占整个 Java Heap 的大小,默认值为 60%;
-XX:G1HeapRegionSize:设置每个内存块的大小,单位 MB,需要为 1、2、4、8、16、
32 中的某个值,默认是堆内存的1/2000;
-XX:ConcGCThreads:与 业务线程一起执行的 GC 线程数量,默认是 Java 线程的 1/4;
-XX:+InitiatingHeapOccupancyPercent:触发老年代回收的内存使用阈值,默认为堆大小的
45%;
-XX:G1HeapWastePercent:停止GC回收的内存使用阈值,默认是堆大小的 5%;
-XX:MaxGCPauseMills:预期G1 GC每次执行操作的暂停时间,单位是毫秒,G1 GC会尽
量(不是一定)保证控制在这个范围内;
G1 GC的注意事项:
某些情况下G1会退化使用串行收集器来完成垃圾的清理工作,仅仅使用单线程来完成 GC 工
作,GC 暂停时间将大幅增加;
1,晋升失败
没有足够的内存供存活对象或晋升对象使用,由此触发了 Full GC(to-space exhausted/to-
space overflow)。
解决办法:
a) 增加 –XX:G1ReservePercent 选项的值(并相应增加总的堆大小)增加预留内存量。
b) 通过减少 –XX:InitiatingHeapOccupancyPercent 提前启动标记周期。
c) 也可以通过增加 –XX:ConcGCThreads 选项的值来增加并行标记线程的数目。
2,巨型对象分配失败
当巨型对象找不到合适的空间进行分配时,就会启动 Full GC,来释放空间。
解决办法:增加内存或者增大 -XX:G1HeapRegionSize
适用场景:
内存比较大,且希望GC时间,整体可控;
ZGC
启动参数:-XX:+UnlockExperimentalVMOptions -XX:+UseZGC -Xmx16g
特点:
GC 最大停顿时间不超过 10ms;
堆内存支持范围广,小至几百 MB 的堆空间,大至 4TB 的超大堆内存(JDK13 升至 16TB)
与 G1 相比,应用吞吐量下降不超过 15%
当前只支持 Linux/x64 位平台,JDK15 后支持 MacOS 和Windows 系统;
JDK使用的默认GC
JDK6,JDK7,JDK8 默认使用并行GC;
JDK9以及以上上默认使用并行G1 GC;
GC算法的选择
选择正确的 GC 算法,唯一可行的方式就是去尝试,一般性的指导原则:
1,如果系统考虑吞吐优先,CPU 资源都用来最大程度处理业务,用 并行 GC;
2,如果系统考虑低延迟有限,每次 GC 时间尽量短,用 CMS GC;
3,如果系统内存堆较大,同时希望整体来看平均 GC 时间可控,使用 G1 GC。
从内存大小的角度考量:
1,一般 4G 以上,算是比较大,用 G1 的性价比较高。
2,一般超过 8G,比如 16G-64G 内存,非常推荐使用 G1 GC。