Java垃圾回收

内存回收常用算法:

  1. 引用计数
  2. 可达性分析(GC Root)

如何判断一个对象的死亡?

1
当一个对象不再被任何存活的对象继续引用的时候,这个对象就死亡了。

引用计数

给每一个对象添加一个计数器

  • 当有对象引用它的时候,计数器+1。
  • 当有对象取消对它的引用时,计数就会-1。
1
当计数器的值为 0 时,即说明没有对象引用它,也就是这个对象死亡了。

这种算法简单,但是有个重大缺陷,那就是无法解决循环引用问题。

什么是循环引用问题呢?

比如对象A 引用 对象B,对象B 引用 对象A,那么 对象A 和 对象B 的计数器都为1。但是如果后续的运行环境再也用不到对象A 和 对象B,那么就造成了内存泄漏。

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
public class ReferenceCountGC {
public Object instance = null;

private static final int _1MB = 1024 * 1024;

// 每个对象中包含2M的成员,方便观察
private byte[] bigSize = new byte[2 * _1MB];

public void isAlive() {
System.out.println("Yes, i am alive");
}

public static void main(String[] args) {
ReferenceCountGC objA = new ReferenceCountGC();
ReferenceCountGC objB = new ReferenceCountGC();
objA.instance = objB.instance;
objB.instance = objA.instance;

// 取消对对象的引用
objA = null;
objB = null;
// 是否进行垃圾回收
System.gc();
// 没有执行,说明Java没有使用这种算法,而是使用了我们后面说的可达性算法
if (objA != null) {
objA.isAlive();
}
}
}

可达性分析

别名:根搜索算法,追踪性垃圾收集,GC Root。

原理

首先,我们选取一些对象,这些对象是存活的,也被称为 GC Roots,然后根据这些对象的引用关系,凡是直接或者间接跟 GC Roots 相关联的对象,都是存活的。就像图中的连通性判断一样。

如何选取GC Roots

在堆结构周围的一些结构,其中引用的对象可以作为GC Roots,具体可以概括为:

  • 虚拟机栈上(确切的说,是栈帧上的本地变量表)所引用的对象
  • 本地方法栈引用的对象
  • 方法区中的静态属性,常量引用
  • Java 虚拟机的内部引用,常用数据类型的 Class 对象,常驻的异常对象,系统类加载器
  • 所有被同步锁持有的对象
  • 除此之外,还有一些临时的 GC Roots 可以加入进来。比如老年代中的对象一般都存活时间比较久,也就是大概率是活着的对象,也可临时作为 GC Roots。

标记死亡后如何自救

被判定为不可达的对象,并不立刻死亡。它仍然有次机会进行自救,需要重写 finalize()。

也就是可达性算法的逻辑大致是这样的:

  1. 第一次进行标记,凡是不可达 GC Roots 的对象,都暂时判定为死亡,只是暂时。
  2. 检查暂时被判定为死亡对象,检查是否有重写 finalize()方法:
    • 如果有,则触发,对象可以在里面完成自救。
    • 如果没有自救成功 或者 没有重写 finalize()方法,则宣告这个对象的死亡。
  3. 除此之外,这个对象中的 finalize()方法,只能被调用一次,一生只有一次自救机会。
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
public class FinalizeEscapeGC {
public static FinalizeEscapeGC SAVE_HOOK = null;

private byte[] bigSize = new byte[5 * 1024 * 1024];

public void isAlive() {
System.out.println("Yes, i am alive");
}

@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("Finalize method executed");
FinalizeEscapeGC.SAVE_HOOK = this;
}

public static void main(String[] args) throws InterruptedException {
SAVE_HOOK = new FinalizeEscapeGC();
SAVE_HOOK = null;
System.gc();
Thread.sleep(500);
if (SAVE_HOOK != null) {
SAVE_HOOK.isAlive();
SAVE_HOOK = null;
System.gc();
if (SAVE_HOOK == null) {
System.out.println("Dead");
}
} else {
System.out.println("Dead");
System.gc();
}
}
}
1
2
3
Finalize method executed
Yes, i am alive
Dead
0%