JVM原理之垃圾回收算法及垃圾收集器
6. 垃圾回收算法
6.1 如何确定对象是垃圾
要想进行垃圾回收,必须知道哪些对象是垃圾,那怎么知道呢?
6.1.1 引用计数法
-
如果一个对象被引用,就说明该对象不是垃圾;如果一个对象没有任何指针对其引用,它就是垃圾。
-
缺点:互相引用的对象将永远不会被回收。如:对象A中有对象B的引用,B也亦然;
6.1.2 可达性分析
那什么是可达性分析呢?那些东西可以作为GCRoot呢?
-
从GCRoot开始,可以连接的对象不会被回收,不能连接到即不可达的对象会被回收。
-
可以作为GCRoot的对象的有 :
-
静态变量所引用的对象
-
虚拟机栈中本地变量引用对象
-
常量池中常量,也就是方法区的常量
-
本地方法栈中变量引用的对象
-
Thread
-
类加载器
如下图所示,浅绿色标识对象为不可达对象,会确定为垃圾。
-
-
从GCRoot 出发,能链接到的类不会回收,连接不到即不可达的对象会被回收。
6.2 垃圾回收算法
前面我们了解了是如何确定对象时垃圾的方法,那JVM是如何进行垃圾回收算法的呢?它具体有哪几种垃圾回收算法?
6.2.1 复制回收算法
什么是复制回收算法呢?
-
将内存分为大小相等的两块,只有其中一方存放数据;如下图所示:
-
当存放数据的一方内存空间用完了,将还存活的对象复制到另一方 ,然后把已经使用过的内存清理干净。
优点:清理效率高;
缺点:会浪费空间,空间利用率低
6.2.2 标记清除算法
什么是标记清除算法?
-
标记阶段
找出内存中需要清理的对象,然后进行标记
-
清除阶段
将标记出来的对象进行清除
-
缺点
- 标记会扫描整个内存空间比较耗时。
- 有内存碎片(标记清除后会有不连续的内存碎片,空间碎片太多如果后续有大对象需要空间,无法找到连续的内存空间,进而不得不提前触发GC。)
6.2.3 标记清除压缩算法
那什么是标记清除压缩算法(也叫做标记整理算法)呢?
-
标记阶段
找出内存中需要清理的对象,然后进行标记
-
清除压缩阶段(整理阶段)
将标记出来的对象进行清除,并将存活的对象向一端移动
缺点:标记阶段 和 清除压缩阶段比较耗时。
6.2.4 分代收集算法
上面我们了解到了三种垃圾回收算法,那么我们在堆内存中使用哪个呢?
- Young区:复制回收算法(因为Young区对象生命周期短,效率高)
- Old区:标记清除或者标记清除压缩(Old区对象生命存活时间长,复制来复制去没必要,不如标记后进行处理)
7.垃圾收集器
前面我们知道了各种垃圾算法,而垃圾收集器是对于垃圾回收算法的实现,那JVM中具体有哪些垃圾收集器呢?
7.1 Serial收集器
Serial收集器是最基本最悠久的收集器,曾经(JDK1.3)是虚拟机新生代垃圾收集的唯一选择。
它是单线程收集器,只会使用一个CPU或者一条线程去完成垃圾回收,并且在垃圾回收的时候会暂停别的线程
- 优点:简单高效,有很高的单线程收集效率;
- 缺点:收集过程中暂停所有线程
- 使用算法:复制算法
- 适用区域:年轻代- Young区
- 使用标准:单CPU,单机程序,内存小
-XX:+UseSerialGC
- 图解Serial垃圾回收器
7.2 ParNew收集器
相当于Serial收集器的多线程版本
-
优点:多CPU下,比Serial效率高
-
缺点:收集过程暂停所有应用程序线程,单CPU时Serial效率比它高
-
使用算法:复制算法
-
适用区域:年轻代-Young区
-
多CPU,希望快速响应,不希望有停顿时间
-
-XX:UseParNewGC
或者-XX:UseParallelGC
-
-
图解 ParNew GC
7.3 Parallel Scavenge 收集器
Parallel Scavenge 收集器与 ParNew 收集器唯一的区别是它更关注系统的吞吐量
吞吐量 = 用户代码运行时间 / (用户代码运行时间 + GC时间);如虚拟机一共运行了100分钟,GC时间为1分钟,吞吐量 = (100-1)/ 100 = 99% ; 吞吐量越大,意味着GC时间越短,则用户程序就可以充分利用CPU资源,尽快完成运算任务。
- 使用标准:多CPU,大的吞吐量
-XX:+UseParallelGC
7.4 CMS 收集器
官网对于CMS收集器的解释:https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/cms.html#concurrent_mode_failure
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间
为目标的收集器。采用的是标记-清除算法
,分为四步
-
1.初始标记,标记从GCRoot能关联的对象 ,stop the world , 速度很快
-
2.并发标记,和用户线程一起进行,从GCRoot 往下追踪
-
3.重新标记, 暂停用户线程,重新标记对象
-
4.并发清理,用户线程和GC线程一起执行
-
优点:并发收集、低停顿
-
缺点:产生大量的空间碎片、并发阶段会降低吞吐量
-
启用命令:
-XX:+UseConcMarkSweepGC
7.5 SerialOld收集器
SerialOld 收集器是 Serial 收集器在老年代的版本,也是单线程的,采用的算法是 标记-清除-压缩算法
,运行过程和Serial收集器一样。
- 图解表示 Serial收集器 与 SerialOld收集器 搭配使用效果
7.6 Parallel Old 收集器
Parallel Old 收集器 是 Parallel Scavenge收集器的老年代的版本,使用多线程
和 标记-清除-压缩算法
进行垃圾回收。依旧是吞吐量
优先原则
- 图解表示 Paraller ScavengeS收集器 与 Parallel Old收集器配合使用图解;
7.7 G1收集器
G1收集器通过多种技术实现了高性能和暂停时间目标。
执行过程:堆被划分为一组大小相等的堆区域,每个堆区域都有一个连续的虚拟内存范围。它也会进行全局标记,标记完成后,它就会知道哪些区域大部分为空。那么他在收集的时候会优先收集这些区域。G1使用暂停预测模型来满足用户定义的暂停时间(-XX:MaxGCPauseMillis
)目标,并根据指定的暂停时间选择要收集的区域数。
G1首要重点是为运行过程中需要大的堆内存并且GC延迟有要求的应用程序提供解决方案,这意味着堆大小约为6 GB或更大,并且稳定且可预测的暂停时间低于0.5秒。
以下情况可切换为G1收集器:
- 超过50%的堆内存被实时数据占用
- 对象分配率或提升率差率很大
- 希望 减少 程序垃圾收集时间或压缩暂停 时间(大于0.5到1秒)
工作过程分为如下几步:
- 初始标记,标记下GC Roots能够关联的对象,需要暂停用户线程
- 并发标记,从GC Root进行可达性分析,找出存活对象,与用户线程并发执行
- 最终标记,修正在并发标记阶段因为用户程序执行而变动的数据,需要暂停用户线程
- 筛选回收,对各个Region的回收价值和成本进行排序,根据GC停顿时间制定回收计划。
- 启用G1:
-XX:+UseG1GC
,设置回收停顿时间,-XX:MaxGCPauseMillis=200
,默认为200毫秒 - 优点:1.没有内存碎片 ;2.可以控制垃圾回收停顿时间
8 如何选择合适的垃圾收集器
官方地址:https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/collectors.html#sthref28
- 优先调整堆的大小让服务器自己来选择
- 如果内存小于100M,使用串行收集器
-XX:+UseSerialGC
- 如果响应时间比整体吞吐量更重要,并且垃圾收集暂停时间必须保持小于1秒,可以使用
-XX:+UseConcMarkSweepGC
或者 选择-XX:+UseG1GC