# 垃圾回收概述

  • 什么场景下该使用什么垃圾回收策略?
  • 垃圾回收发生在哪些区域?
  • 对象在什么时候能够被回收?

# 什么场景下该使用什么垃圾回收策略?

一般情况下,在对内存要求苛刻的场景,想办法提高对象回收效率,多回收掉一些对象,腾出更多内存

在 CPU 使用率较高的情况下:降低高并发时垃圾回收的频率,让 CPU 更多的去执行你的业务而不是垃圾回收

# 垃圾回收发生在哪些区域?

image.png
线程隔离区域是不需要考虑垃圾回收的,因为随着线程的创建而创建,销毁而销毁。
所以 堆、方法区是需要做垃圾回收的:

  • 堆:主要回收的事创建的对象
  • 方法区:主要回收的是废弃的常量以及不使用的类

# 对象在什么时候能够被回收?

一般来说有两种方法来判定:

  • 引用计数法
  • 可达性分析

# 引用计数法:

通过对象的引用计数器来判断该对象是否被引用
image.png
如上图所示:A 引用了 B,那么 B 上的计数器就 +1,B 引用 C,那么 C 上的计数器就 + 1,
所以实现起来相对简单,判断对象是否能够被回收也很简单,如果遇到有循环引用的情况:
image.png
图一是 A 引用了 B,然后 B C D 之间又是循环引用,当 A 不引用 B 的时候,由于 B、C、D 是循环引用,会导致 B、C、D 都无法被回收

# 可达性分析

以跟对象(GC Roots)作为起点向下搜索,走过的路径被称为 引用链(Reference Chain),如果某个对象到根对象没有引用链相连时,就认为这个对象是不可达的,可以回收
image.png
如上图所示:1、2、3、4 是存活对象,因为有引用链可达根对象,5、6、7 可回收

# GC Roots 包括哪些对象?

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象(一般是局部变量)
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中 JNI(即 Native 方法)引用的对象

# 引用

  • 强引用(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);
1
2

不影响对象的生命周期,如果一个对象 只有 虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
虚引用主要用来跟踪对象被垃圾回收器回收的活动,必须和引用对列(ReferenceQueue)配合使用。
当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动

# 可达性算法注意点

一个对象即使不可达,也不一定会被回收,完整的回收流程大概如下
image.png
下面这一段代码,来帮助理解回收流程

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 可用");
        }
    }
}

1
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
1
2
3
  1. 第一次将 obj 置为空,然后执行了 gc()操作,触发了 finalize 调用,但是在这里面又对 obj 进行了赋值,所以 obj 没有被回收掉
  2. 第二次将 obj 置为空,发现没有调用 finalize 方法了,然后对象就被回收掉了

这次会走图上的 「无必要」分支,因为 finalize 已经被调用过一次了,所以就被直接回收了

WARNING

这里有一个严重的写法:
就是第二次将 obj 重置为 null 的时候,万一忘记了,没有人工重置,就会导致这个对象永远无法被回收掉

# finalize() 的建议

  • 尽量避免使用 finalize()方法,操作不当可能会导致问题
  • finalize()优先级低,何时被调用无法确定,因为什么时间发生 GC 不确定

# 拓展阅读

# 引用

  • 软引用

    JDK 中的软引用(SoftReference)是一种弱化了引用强度的引用类型,它可以让被软引用关联的对象在内存不足时被回收,但在内存充足时,被软引用关联的对象则不会被回收。
    软引用通常用于缓存和高速缓存的实现中。它可以让缓存中的对象在内存不足时被回收,避免了内存溢出的风险。另外,它还可以用于实现一些需要动态加载和卸载资源的场景,比如类加载器中的缓存。在这些场景中,软引用可以帮助我们在内存不足时自动释放缓存中的对象,而不需要手动清理缓存。
    需要注意的是,由于软引用的回收时间是不确定的,因此在使用软引用时,需要针对具体的应用场景进行评估和测试,以确保软引用的使用不会影响系统的性能和稳定性。

  • 弱引用

    JDK 中的弱引用(WeakReference)是一种比软引用更弱化了引用强度的引用类型,它可以让被弱引用关联的对象在下一次垃圾回收时被回收。
    弱引用通常用于实现一些需要动态创建和销毁对象的场景,比如缓存中的对象。在这些场景中,弱引用可以让缓存中的对象在没有被使用时被自动回收,避免了内存占用过多的问题。另外,弱引用也可以用于实现一些需要动态更新状态的场景,比如任务管理器中的任务对象,当任务完成后,任务对象就可以被垃圾回收。
    需要注意的是,由于弱引用的回收时间是不确定的,因此在使用弱引用时,需要针对具体的应用场景进行评估和测试,以确保弱引用的使用不会影响系统的性能和稳定性。同时,由于弱引用可能在任何时候被回收,因此在使用弱引用时需要特别注意空指针异常的处理。

  • 虚引用

    JDK 中的虚引用(PhantomReference)是一种更加弱化了引用强度的引用类型,它与弱引用和软引用不同,它并不会影响到对象的生命周期。相反,虚引用的主要作用是在对象被回收时收到一个通知。
    虚引用通常用于一些需要跟踪对象回收的场景,比如对象资源的管理。在这些场景中,虚引用可以帮助我们在对象被回收时收到通知,从而执行一些资源的清理和释放操作,比如关闭文件、释放网络连接等。另外,虚引用还可以用于一些需要跟踪对象生命周期的场景,比如对象池中的对象管理。
    需要注意的是,虚引用并不会影响到对象的生命周期,因此不能通过虚引用来获取对象。同时,由于虚引用的使用需要维护一个额外的对象引用队列,因此在使用虚引用时需要考虑额外的内存开销和性能影响。虚引用的使用场景比较特殊,需要在特定场景下评估和测试其效果。