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

垃圾收集(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首先枚举根节点,找到一些为了保证程序能正常运行所必须要存活的对象,然后以这些对象为根,根据引用关系开始向下搜寻,存在直接或间接引用链的对象就存活,不存在引用链的对象就回收。

并发CAS是指在高并发环境下的一种无锁算法,它可以实现多个线程之间的变量同步,而不需要使用锁来进行线程阻塞。这种机制在处理高并发访问时非常常见和常用,它可以有效地提高并发性能。 简单来说,并发CAS的工作原理是通过比较内存中的值与期望值是否相等来确定是否需要更新这个值。如果相等,则使用新的值来更新内存中的值;如果不相等,则说明其他线程已经修改了这个值,当前线程需要重新读取内存中的值并重新比较,直到成功为止。 举个例子来说明,并发CAS的过程:假设有两个线程同时执行incrementAndGet()操作,他们都希望将某个变量的值增加1。首先,线程1读取内存中的值,并将期望值设为读取到的值。接着,线程1使用CAS操作来比较内存中的值与期望值是否相等,如果相等,则将新的值更新到内存中。与此同时,线程2也在执行相同的操作。如果线程1和线程2同时执行CAS操作,那么只有一个线程能够成功更新内存中的值,而另一个线程需要重新读取内存中的值并重新比较。这样就避免了锁机制下的线程阻塞,提高了并发性能。 总的来说,并发CAS是一种非常有效的并发优化手段,它可以在高并发环境下提供更好的性能和可伸缩性。然而,需要注意的是,并发CAS并不是适用于所有并发场景,它对于一些复杂的并发问题可能无法提供完全准确的结果。这需要开发者在使用并发CAS时对多线程并发问题有一定的了解和处理经验。<span class="em">1</span><span class="em">2</span><span class="em">3</span><span class="em">4</span>
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员小潘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值