大白话理解可达性分析算法

垃圾收集(Garbage Collection,下文简称GC)是Java有别于其他编程语言的一大特点,GC主要考虑的有三个问题:

  • 哪些内存需要回收?
  • 什么时候回收?
  • 如何回收?

在这里插入图片描述
今天咱们主要聊聊JVM是如何判断对象可以被回收的。

常用的算法有两种:

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

引用计数算法

在对象中添加一个引用计数器,每当新加一个引用时,计数器就加1,当引用失效时,计数器就减1。任何时刻只要计数器为0就代表对象没有引用可以被回收。

这种算法实现简单,判断高效,但是有一些缺点:
在这里插入图片描述
主流的商用JVM实现都没有采用这种方式。


可达性分析算法

目前主流的商用JVM都是通过可达性分析来判断对象是否可以被回收的。
在这里插入图片描述

这个算法的基本思路是:

通过一系列被称为「GC Roots」的根对象作为起始节点集,从这些节点开始,通过引用关系向下搜寻,搜寻走过的路径称为「引用链」,如果某个对象到GC Roots没有任何引用链相连,就说明该对象不可达,即可以被回收。

初看这段话是不是一脸懵呢?笔者当初也是的,完全不知道什么意思,后面才慢慢理解。

要想理解可达性算法,首先要想明白几个问题:
在这里插入图片描述

1、什么是对象可达?

对象可达指的就是:双方存在直接或间接的引用关系
根可达或GC Roots可达就是指:对象到GC Roots存在直接或间接的引用关系。
如下代码:

public class MyObject {
	private String objectName;//对象名
	private MyObject refrence;//依赖对象

	public MyObject(String objectName) {
		this.objectName = objectName;
	}

	public MyObject(String objectName, MyObject refrence) {
		this.objectName = objectName;
		this.refrence = refrence;
	}

	public static void main(String[] args) {
		MyObject a = new MyObject("a");
		MyObject b = new MyObject("b");
		MyObject c = new MyObject("c");
		a.refrence = b;
		b.refrence = c;

		new MyObject("d", new MyObject("e"));
	}
}

创建了5个对象,他们之间的引用关系如图:
在这里插入图片描述
假设a是GC Roots的话,那么b、c就是可达的,d、e是不可达的。

2、GC Roots是什么?

垃圾回收时,JVM首先要找到所有的GC Roots,这个过程称作 「枚举根节点」 ,这个过程是需要暂停用户线程的,即触发STW。
然后再从GC Roots这些根节点向下搜寻,可达的对象就保留,不可达的对象就回收。
那么,到底什么是GC Roots呢?

GC Roots就是对象,而且是JVM确定当前绝对不能被回收的对象(如方法区中类静态属性引用的对象 )。
只有找到这种对象,后面的搜寻过程才有意义,不能被回收的对象所依赖的其他对象肯定也不能回收嘛。

当JVM触发GC时,首先会让所有的用户线程到达安全点SafePoint时阻塞,也就是STW,然后枚举根节点,即找到所有的GC Roots,然后就可以从这些GC Roots向下搜寻,可达的对象就保留,不可达的对象就回收。

即使是号称几乎不停顿的CMS、G1等收集器,在枚举根节点时,也是要暂停用户线程的。

GC Roots是一种特殊的对象,是Java程序在运行过程中所必须的对象,而且是根对象。
那么,哪些对象可以成为GC Roots呢?

3、哪些对象可以作为GC Roots?

可以作为GC Roots的对象可以分为两大类:全局对象和执行上下文。
在这里插入图片描述
下面就一起来理解一下为什么这几类对象可以被作为GC Roots。
1、方法区静态属性引用的对象
全局对象的一种,Class对象本身很难被回收,回收的条件非常苛刻,只要Class对象不被回收,静态成员就不能被回收。

2、方法区常量池引用的对象
也属于全局对象,例如字符串常量池,常量本身初始化后不会再改变,因此作为GC Roots也是合理的。

3、方法栈中栈帧本地变量表引用的对象
属于执行上下文中的对象,线程在执行方法时,会将方法打包成一个栈帧入栈执行,方法里用到的局部变量会存放到栈帧的本地变量表中。只要方法还在运行,还没出栈,就意味这本地变量表的对象还会被访问,GC就不应该回收,所以这一类对象也可作为GC Roots。

4、JNI本地方法栈中引用的对象
和上一条本质相同,无非是一个是Java方法栈中的变量引用,一个是native方法(C、C++)方法栈中的变量引用。

5、被同步锁持有的对象
被synchronized锁住的对象也是绝对不能回收的,当前有线程持有对象锁呢,GC如果回收了对象,锁不就失效了嘛。

尾巴

总结,可达性分析就是JVM首先枚举根节点,找到一些为了保证程序能正常运行所必须要存活的对象,然后以这些对象为根,根据引用关系开始向下搜寻,存在直接或间接引用链的对象就存活,不存在引用链的对象就回收。

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 博客之星2020 设计师:CY__0809 返回首页