什么是垃圾回收及有哪些垃圾收集器?
作者:徐梦旗,发布于:2023年09月12日 19:00,字数:1.8k,预计阅读:6分钟
1. 垃圾回收原理
1.1. 什么是垃圾?
垃圾是指不再被使用的对象,也就是死亡的对象。
1.2. 为什么需要垃圾回收?
由于内存空间是有限的,需要垃圾回收来释放内存空间,以保证程序的正常运行。
1.3. 如何找到垃圾?
- 引用计数法:当对象被引用时,其引用计数器加一;当去除对象的引用时,引用计数器减一。当引用计数器的值为零时,代表该对象是垃圾对象。引用计数法不能解决循环依赖的问题。
- 可达性分析:当对象不能被
GC Roots
直接或间接地引用时,代表该对象是垃圾对象。GC Roots
搜索走过的路径被称为引用链。
如上图所示,可达性分析结果如下:
- 对象
R1
,R2
是GC Roots
,故存活。 - 对象
A
被R1
直接引用,故存活。 - 对象
B
,F
并未被R1
或R2
直接或间接地引用,故死亡。 - 对象
C
被R2
直接引用,及被R1
间接引用,故存活。 - 对象
D
被R1
间接引用,其引用链为R1 → A → D
,故存活。
1.4. 哪些对象可以作为GC Roots?
- 虚拟机栈的栈帧中局部变量表所引用的对象。
- 本地方法栈中
JNI
引用的对象。 - 方法区中类静态属性引用的对象。
- 方法区中常量引用的对象。
- 虚拟机内部的引用。
1.5. 有哪些对象引用?
- 强引用[1](Strong Reference),不会发生垃圾回收直至发生OOM。
- 软引用(Soft Reference),内存不足时会被垃圾回收,适合用作缓存。
- 弱引用(Weak Reference),在下次GC时会被垃圾回收,如
ThreadLocal
的实现。 - 虚引用(Plantom Reference),在下次GC时会被垃圾回收,用作回调机制。
1.6. ThreadLocal为什么使用弱引用?
ThreadLocal
是指线程本地变量,即为每个线程提供一份变量副本。本质上是在每个线程中都维护了一个ThreadLocalMap
,内部Entry
的键reference
是ThreadLocal
,内部Entry
的值value
是ThreadLocal
对应的值。
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
ThreadLocal
使用的引用是弱引用,当客户端的强引用不再引用时,会在下次GC时会被回收,从根本上避免了内存泄漏。ThreadLocal
对应的值使用的是强引用,在使用完后需要手动调用ThreadLocal#remove
移除引用,来避免发生内存泄漏。
2. 垃圾收集器
2.1. 有哪些垃圾回收算法?
- 标记-清除算法(Mark-Sweep)。
- 步骤:根据可达性分析算法标记垃圾对象,并清除垃圾对象。
- 利弊:会产生内存碎片。
- 适用场景:由于需要对垃圾对象进行标记和清除,适合垃圾对象较少的情况,即老年代。
- 标记-复制算法(Copying)。
- 步骤:根据可达性分析算法标记存活对象,并将其复制到另一区域,最后对原先的整个区域进行清除。
- 利弊:不会产生内存碎片,但会浪费一部分的内存空间。
- 适用场景:由于需要复制存活对象,适合存活对象校少的情况,即新生代。
- 标记-整理算法(Mark-Compact)。
- 步骤:根据可达性分析算法标记存活对象,并将存活对象移动到一端,最后清除剩余内存区域。
- 利弊:不会产生内存碎片,但整理步骤是负重式的。
- 适用场景:由于需要对垃圾对象进行标记和清除,适合垃圾对象较少的情况,即老年代。
2.2. 什么是Stop The World?什么是safe point?
STW
(Stop The World,STW)是指在垃圾回收的过程中,会在安全点(Safe points)处暂停用户线程,避免产生新的垃圾对象。安全点主要在以下位置设置:
- 循环的末尾。
- 方法返回前。
- 调用方法的call之后。
- 抛出异常的位置。
2.3. 有哪些垃圾收集器?
根据弱分代假说,新生代中存活对象较少,适合使用标记-复制算法。新生代的垃圾收集器有:
- Serial[2],基于标记-复制算法的串行新生代垃圾收集器,会
STW
。 - Parallel Scavenge[3],基于标记-复制算法的并行新生代垃圾收集器,会STW,吞吐量优先。
- ParNew,基于标记-复制算法的并行新生代垃圾收集器,会
STW
,Serial的多线程版。
根据强分代假说,老年代中垃圾对象较少,适合使用标记-清除或标记-整理算法。老年代的垃圾收集器有:
- Serial Old[2],基于标记-整理算法的串行老年代垃圾收集器,会
STW
。 - Parallel Old[3],基于标记-整理算法的并行老年代垃圾收集器,会
STW
,吞吐量优先。 - CMS[4],基于标记-清除算法的并发老年代垃圾收集器,减少
STW
,响应时间优先。
相比于传统的垃圾收集器,整堆垃圾收集器取消了物理上对新生代和老年代的划分,而是逻辑上使用Region
代替Eden
、Survivor
和Old
等。整堆的垃圾收集器有:
- G1[5],全局采用标记-整理算法,局部使用标记-复制算法的整堆垃圾收集器,减少
STW
,关注吞吐量和低延迟,适用于大内存的服务端应用。 - ZGC,基于标记-整理算法的整堆垃圾收集器,关注低延迟。
虚拟机参数:
-XX:+UseSerialGC
:指定垃圾收集器为Serial搭配Serial Old。-XX:+UseParallelGC
:指定垃圾收集器为Parallel Scavenge搭配Serial Old。-XX:+UseParallelOldGC
:指定垃圾收集器为Parallel Scavenge搭配Serial Old。-XX:+UseParNewGC
:指定垃圾收集器为ParNew搭配Serial Old,于JDK9移除。-XX:+UseConcMarkSweepGC
:指定垃圾收集器为ParNew搭配CMS,加Serial Old;-XX:+UseConcMarkSweepGC -XX:-UseParNewGC
:指定垃圾收集器为Serial搭配CMS,于JDK9移除。-XX:+UseG1GC
:指定垃圾收集器为G1。-XX:+UseZGC
:指定垃圾收集器为ZGC。
2.4. CMS垃圾收集器垃圾回收的过程是怎样的?
- 初始标记,标记
GC Roots
对象,耗时短,会STW
; - 并发标记,标记
GC Roots
引用链上的对象,耗时长,不会STW
; - 重新标记,修复并发标记期间新产生的对象,会
STW
; - 并发清除,清除垃圾对象,不会
STW
。
2.5. G1垃圾收集器垃圾回收的过程是怎样的?
- 初始标记,标记
GC Roots
对象,耗时短,会STW
; - 并发标记,标记
GC Roots
引用链上的对象,耗时长,不会STW
; - 最终标记,修复并发标记期间新产生的对象,会
STW
; - 筛选回收,对各个
Region
的回收价值和成本排序,根据用户期望GC停顿时间确定回收计划,会STW
。