# 垃圾回收概述
- 什么场景下该使用什么垃圾回收策略?
- 垃圾回收发生在哪些区域?
- 对象在什么时候能够被回收?
# 什么场景下该使用什么垃圾回收策略?
一般情况下,在对内存要求苛刻的场景,想办法提高对象回收效率,多回收掉一些对象,腾出更多内存
在 CPU 使用率较高的情况下:降低高并发时垃圾回收的频率,让 CPU 更多的去执行你的业务而不是垃圾回收
# 垃圾回收发生在哪些区域?
线程隔离区域是不需要考虑垃圾回收的,因为随着线程的创建而创建,销毁而销毁。
所以 堆、方法区是需要做垃圾回收的:
- 堆:主要回收的事创建的对象
- 方法区:主要回收的是废弃的常量以及不使用的类
# 对象在什么时候能够被回收?
一般来说有两种方法来判定:
# 引用计数法:
通过对象的引用计数器来判断该对象是否被引用
如上图所示:A 引用了 B,那么 B 上的计数器就 +1,B 引用 C,那么 C 上的计数器就 + 1,
所以实现起来相对简单,判断对象是否能够被回收也很简单,如果遇到有循环引用的情况:
图一是 A 引用了 B,然后 B C D 之间又是循环引用,当 A 不引用 B 的时候,由于 B、C、D 是循环引用,会导致 B、C、D 都无法被回收
# 可达性分析
以跟对象(GC Roots)作为起点向下搜索,走过的路径被称为 引用链(Reference Chain),如果某个对象到根对象没有引用链相连时,就认为这个对象是不可达的,可以回收
如上图所示:1、2、3、4 是存活对象,因为有引用链可达根对象,5、6、7 可回收
# GC Roots 包括哪些对象?
# 引用
- 强引用(Strong Reference)
形如 Object obj = new Object()
的引用
只要强引用在,永远不会回收被引用的对象
- 软引用(Soft Reference)
形如 SoftReference<String> sr = new SoftReference<>("hello")
是用来描述一些有用但非必须得对象
软引用关联的对象,只有在内存不足的时候才会回收
比如可以用来实现一些缓存:网页缓存、图片缓存等
- 弱引用(Weak Reference)
形如 WeakReference<String> sr = new WeakReference<>("hello")
弱引用也是用来描述非必须对象的
无论内存是否充足,都会回收被弱引用关联的对象
- 虚引用(Phantom Reference)
ReferenceQueue<String> queue = new ReferenceQueue<>();
PhantomReference<String> pr = new PhantomReference<>("hello",queue);
2
不影响对象的生命周期,如果一个对象 只有 虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
虚引用主要用来跟踪对象被垃圾回收器回收的活动,必须和引用对列(ReferenceQueue)配合使用。
当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动
# 可达性算法注意点
一个对象即使不可达,也不一定会被回收,完整的回收流程大概如下
下面这一段代码,来帮助理解回收流程
package cn.mrcode.demo.boodadmin.jvm;
/**
* @author mrcode
* @date 2023/4/3 22:27
*/
public class GCTest1 {
private static GCTest1 obj;
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("finalize 被调用了");
obj = this;
}
public static void main(String[] args) throws InterruptedException {
obj = new GCTest1();
obj = null;
System.gc();
Thread.sleep(1000L);
if (obj == null){
System.out.println("obj == null");
}else {
System.out.println("obj 可用");
}
Thread.sleep(1000L);
obj = null;
System.gc();
if (obj == null){
System.out.println("obj == null");
}else {
System.out.println("obj 可用");
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
测试输出信息如下
finalize 被调用了
obj 可用
obj == null
2
3
- 第一次将 obj 置为空,然后执行了
gc()
操作,触发了 finalize 调用,但是在这里面又对 obj 进行了赋值,所以 obj 没有被回收掉 - 第二次将 obj 置为空,发现没有调用 finalize 方法了,然后对象就被回收掉了
这次会走图上的 「无必要」分支,因为 finalize 已经被调用过一次了,所以就被直接回收了
WARNING
这里有一个严重的写法:
就是第二次将 obj 重置为 null 的时候,万一忘记了,没有人工重置,就会导致这个对象永远无法被回收掉
# finalize() 的建议
- 尽量避免使用
finalize()
方法,操作不当可能会导致问题 finalize()
优先级低,何时被调用无法确定,因为什么时间发生 GC 不确定
# 拓展阅读
# 引用
软引用
JDK 中的软引用(SoftReference)是一种弱化了引用强度的引用类型,它可以让被软引用关联的对象在内存不足时被回收,但在内存充足时,被软引用关联的对象则不会被回收。
软引用通常用于缓存和高速缓存的实现中。它可以让缓存中的对象在内存不足时被回收,避免了内存溢出的风险。另外,它还可以用于实现一些需要动态加载和卸载资源的场景,比如类加载器中的缓存。在这些场景中,软引用可以帮助我们在内存不足时自动释放缓存中的对象,而不需要手动清理缓存。
需要注意的是,由于软引用的回收时间是不确定的,因此在使用软引用时,需要针对具体的应用场景进行评估和测试,以确保软引用的使用不会影响系统的性能和稳定性。弱引用
JDK 中的弱引用(WeakReference)是一种比软引用更弱化了引用强度的引用类型,它可以让被弱引用关联的对象在下一次垃圾回收时被回收。
弱引用通常用于实现一些需要动态创建和销毁对象的场景,比如缓存中的对象。在这些场景中,弱引用可以让缓存中的对象在没有被使用时被自动回收,避免了内存占用过多的问题。另外,弱引用也可以用于实现一些需要动态更新状态的场景,比如任务管理器中的任务对象,当任务完成后,任务对象就可以被垃圾回收。
需要注意的是,由于弱引用的回收时间是不确定的,因此在使用弱引用时,需要针对具体的应用场景进行评估和测试,以确保弱引用的使用不会影响系统的性能和稳定性。同时,由于弱引用可能在任何时候被回收,因此在使用弱引用时需要特别注意空指针异常的处理。虚引用
JDK 中的虚引用(PhantomReference)是一种更加弱化了引用强度的引用类型,它与弱引用和软引用不同,它并不会影响到对象的生命周期。相反,虚引用的主要作用是在对象被回收时收到一个通知。
虚引用通常用于一些需要跟踪对象回收的场景,比如对象资源的管理。在这些场景中,虚引用可以帮助我们在对象被回收时收到通知,从而执行一些资源的清理和释放操作,比如关闭文件、释放网络连接等。另外,虚引用还可以用于一些需要跟踪对象生命周期的场景,比如对象池中的对象管理。
需要注意的是,虚引用并不会影响到对象的生命周期,因此不能通过虚引用来获取对象。同时,由于虚引用的使用需要维护一个额外的对象引用队列,因此在使用虚引用时需要考虑额外的内存开销和性能影响。虚引用的使用场景比较特殊,需要在特定场景下评估和测试其效果。