想知道对象死没死?看这一篇就够了(引用计数算法、可达性分析算法)

avatar
avatar
云惠网小编
2845
文章
1
评论
2021年4月8日19:18:26 评论 6 次浏览 2768字阅读9分13秒
摘要

如何判断对象已死引用计数算法(Reference Counting)在对象中添加一个引用计数器,每当一个地方引用它,计数器就加一;当引用失效时,计数器就减一;任何时刻计数器为零的对象就是不可能在被使用的。客观的说,引用计数算法虽然占用了一些额外的内存空间来计数,原理简单,效率也很高,但是在Java领域,至少主流的Java虚拟机里面都没有选用引用计数法来进行内存管理,主要原因是,这个算法有很多例外要处理,比如对象之间相互循环引用解决起来就很麻烦。import org.junit.Test;publ

在对象中添加一个引用计数器,每当一个地方引用它,计数器就加一;当引用失效时,计数器就减一;任何时刻计数器为零的对象就是不可能在被使用的。
客观的说,引用计数算法虽然占用了一些额外的内存空间来计数,原理简单,效率也很高,但是在Java领域,至少主流的Java虚拟机里面都没有选用引用计数法来进行内存管理,主要原因是,这个算法有很多例外要处理,比如对象之间相互循环引用解决起来就很麻烦。

方法区的回收

生存还是死亡?

  • 在虚拟机栈(栈中 的本地变量表)中引用的对象,例如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等。
  • 在方法区中类静态属性引用的对象,例如Java类的引用类型静态变量。
  • 在方法区中常量引用的对象,例如字符串常量池(String Table)里的引用。
  • 在本地方法栈中JNI(本地方法)引用的对象。
  • Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(NullPointException、OutOfMemoryError)等,以及系统类加载器。
  • 所有被同步锁(synchronized)持有的对象。
  • 反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。

**基本思路:**通过一系列称为“GC Root”的根对象作为起始节点集,从这些节点开始根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”,如果某个对象到GC Roots间没有任何引用链相连,或者图论的话来说就是从GC Roots到这个对象不可达时,则证明这个对象是不可能再被使用的。算法具体过程如下图所示
在这里插入图片描述
在Java技术体系里面,固定可以作为GC Roots的对象包括以下几种

  • 强引用是最传统的“引用”的定义,是指在程序代码之中普遍存在的引用赋值,即类似“ Object obj = new ObjectQ”这种引用关系。无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象。
  • 软引用是用来描述一些还有用,但非必须的对象。只被软引用关联着的象,在系统将要发生内存溢出异常前,会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存,才会抛出内存溢出异常。在 JDK 1.2 版之后提供了SofReference 类来实现软引用。
  • 弱引用也是用来描述那些非必须对象,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生为止。当垃圾收集器开始工作,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。在 JDK 1.2 版之后提供了WeakReference 类来实现弱引用。
  • 虚引用也称为“幽灵引用”或者“幻影引用”,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的只是为了能在这个对象被收集器回收时收到一个系统通知。在 JDK 1.2 版之后提供了 PhantomReference 类来实现虚引用。

引用计数算法(Reference Counting)

  1. 该类所有的实例都已经无法被回收,也就是Java堆中不存在该类以及任何派生子类的实例
  2. 加载该类的类加载器已经被回收。
  3. 该类对应的Java.lang.class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法

如何判断对象已死

引用

可达性分析算法(Reachability Analysis)

import org.junit.Test;
public class ReferenceCountIngGC {
public Object instance = null;
private static final int _1MB = 1024*1024;
private byte[] bigSize = new byte[2*_1MB];
public static void teseGC(){
ReferenceCountIngGC A = new ReferenceCountIngGC();
ReferenceCountIngGC B = new ReferenceCountIngGC();
A.instance = B;
B.instance = A;
A = null;
B = null;
//假设在这行发生GC,A和B是否能被回收
System.gc();
}
public static void main(String[] args) {
teseGC();
}
}

在这里插入图片描述
由上述GC日志可知,里面包含了“880K->645K(249344K)”表示“GC前java堆已使用容量”–>“GC后java堆的已使用容量(java堆总容量”,这意味着Java虚拟机并没有因为这两个对象互相应用就放弃回收它们,这也说明了Java虚拟机并不是通过引用计数算法来判断对象是否存活的。

方法区的垃圾收集主要回收两部分内容:1.废弃的常量。2.不在使用的类型
不在被使用的类条件:

在JDK1.2版之后,java对引用的概念进行了扩充,将引用分为了强引用(Strongly Reference)、软引用(Soft Reference)、弱引用(Weak Reference)和虚引用(Phantom Reference,引用强度逐次减弱。

即使在可达性分析算法中判定为不可达对象,也不是“非死不可”的,这时候他们暂时还处于“缓刑”阶段,要真正宣告一个对象死亡,至少要经历两次标记过程:如果对即使在可达性分析算法中判定为不可达的对象,也不是“非死不可”的,这时候它们象在进行可达性分析后发现没有与 GC Roots 相连接的引用链,那它将会被第一次标记,随后进行一次筛选,筛选的条件是此对象是否有必要执行 finalize()方法。假如对象没有覆盖finalize()方法,或者 finalize()方法已经被虚拟机调用过,那么虚拟机将这两种情况都视为“没有必要执行”。
如果这个对象被判定为确有必要执行 finalize()方法,那么该对象将会被放置在一个名为 F-Queue 的队列之中,并在稍后由一条由虚拟机自动建立的、低调度优先级的 Finalizer线程去执行它们的 finalize()方法。这里所说的“执行”是指虚拟机会触发这个方法开始运行,但并不承诺一定会等待它运行结束。这样做的原因是,如果某个对象的 finalize0)方法执行缓慢,或者更极端地发生了死循环,将很可能导致 F-Queue 队列中的其他对象永久
处于等待,甚至导致整个内存回收子系统的崩溃。finalize()方法是对象逃脱死亡命运的最后一次机会,稍后收集器将对 F-Queue 中的对象进行第二次小规模的标记,如果对象要在finalize()中成功拯救自己——只要重新与引用链上的任何一个对象建立关联即可。

本文转自 https://blog.csdn.net/qq_45039852/article/details/115488813

腾讯云618
avatar
2w 字长文爆肝 JVM 经典面试题!太顶了! java

2w 字长文爆肝 JVM 经典面试题!太顶了!

如果你是中高级程序员,那我相信你一定被面试官问过JVM。下次再被问到JVM,你直接把老周的这篇文章丢给他吧!话不多说,让我们直接进入主题吧。JVM内存结构,常见异常,调优参数,调优...
JAVA初窥-DAY08 java

JAVA初窥-DAY08

JAVA初窥-DAY08面向过程与面向对象实例化及调用方法和成员变量面向过程与面向对象面向过程:注重的是某件事情过程中的每一个步骤的实现。面向对象:把面向过程中的每一个步骤交给一个...
腾讯云618
匿名

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: