Java 垃圾收集之判断对象是否存活

判断对象是否存活

垃圾收集器工作前需要判断对象是否存活,那么垃圾收集器是如何判断的?判断标准又是什么?前面讲了 Java 内存区域各个部分,其中程序计数器、虚拟机栈、本地方法栈3个区域随线程而生,随线程而灭:栈中的栈帧随着方法进入和退出执行出栈和入栈操作。每个栈帧中分配多少内存基本是在类结构确定下来时就已知的,因此这几个区域内存分配以及回收都具备确定性,不需要过多考虑回收问题,因为方法结束或者线程结束时,内存就跟着回收了。在 Java 堆中,类需要的空间不同,内存需要动态分配,垃圾收集器关注的也是这部分的内存。下面介绍两种判断对象是否存活的方法。

引用计数法

给对象添加一个引用计数器,每当有一个地方引用它时,计数器值加1;当引用失效时,计数器值减1;任何时候计数器值为0的对象就是不可能再被使用的。
引用计数法实现简单,判断效率也高,但是主流的 Java 虚拟机中并没有选用引用计数法来管理内存,最主要的原因就是它很难解决对象之间相互循环引用的问题。
举例说明:

public class ReferenceCountingGC {
    private Object instance = null;
    /**
     * -XX:+PrintGCDetails 打印GC日志
     * @param args
     */
    public static void main(String[] args) {
        ReferenceCountingGC objA = new ReferenceCountingGC();
        ReferenceCountingGC objB = new ReferenceCountingGC();
        objA.instance = objB;
        objB.instance = objA;
        objA = null;
        objB = null;
        System.gc();
    }
}

运行结果:
[GC (System.gc()) [PSYoungGen: 3952K->808K(75776K)] 3952K->816K(249344K), 0.0103407 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[Full GC (System.gc()) [PSYoungGen: 808K->0K(75776K)] [ParOldGen: 8K->710K(173568K)] 816K->710K(249344K), [Metaspace: 3420K->3420K(1056768K)], 0.0042775 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
PSYoungGen total 75776K, used 1951K [0x000000076b580000, 0x0000000770a00000, 0x00000007c0000000)
eden space 65024K, 3% used [0x000000076b580000,0x000000076b767c68,0x000000076f500000)
from space 10752K, 0% used [0x000000076f500000,0x000000076f500000,0x000000076ff80000)
to space 10752K, 0% used [0x000000076ff80000,0x000000076ff80000,0x0000000770a00000)
ParOldGen total 173568K, used 710K [0x00000006c2000000, 0x00000006cc980000, 0x000000076b580000)
object space 173568K, 0% used [0x00000006c2000000,0x00000006c20b1840,0x00000006cc980000)
Metaspace used 3440K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 374K, capacity 388K, committed 512K, reserved 1048576K
从运行结果可以看出,GC日志中包含“3952K->808K”,意味着这两个对象被回收了,这也说明虚拟机不是通过引用计数法判断对象是否存活的。

可达性分析算法

Java是通过可达性分析来判定对象是否存活的。这个算法的基本思想是通过一系列的 “GC Roots” 的对象作为起始点,从这些节点向下搜索,搜索走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连(对象不可达)时,则证明对象是不可用的。如图所示,对象 object5、object6、object7 虽然相互关联,但它们到 GC Roots 是不可达的,所以会被判定为可回收对象。
在这里插入图片描述
Java 中可作为 GC Roots 的对象包括下面几种:

  1. 虚拟机栈(栈帧中的本地变量表)中引用的对象。
  2. 方法区中类静态属性引用的对象。
  3. 方法区中常量引用的对象
  4. 本地方法栈中 JNI(即一般说的 Native 方法)引用的对象。

Java 中的引用

判断对象是否存活都是和“引用”有关的,在 JDK 1.2 之后,Java对引用的概念进行了扩充,将引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)4种,这4种引用强度依次逐渐减弱。

  • 强引用就是指在程序代码中普遍存在的,类似 “Object obj = new Object()” 这类的引用,只要强引用还在,垃圾收集器永远不会回收掉被引用的对象。
  • 软引用是用来描述一些还有用但非必需的对象。对于软引用关联的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。
  • 弱引用也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。
  • 虚引用是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。

对象何时死亡

即使在可达性分析算法中不可达的对象,也并非是 “非死不可” 的,这时候它们暂时处于“缓刑”阶段,要真正宣告一个对象死亡,至少需要经历两次标记过程:如果对象在进行可达性分析后发现没有 GC Roots 相关联的引用链,那它将会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行 finalize() 方法。当对象没有覆盖 finalize() 方法,或者 finalize() 方法已经被虚拟机调用过,虚拟机将这两种情况都视为“没有必要执行”。
如果对象被判定为有必要执行 finalize() 方法,那么这个对象将会放置在一个叫做 F-Queue 的队列之中,并在之后由一个虚拟机自动建立的、低优先级的 Finalizer 线程去执行它。稍后会进行二次标记,如果这是对象还没有被使用,那么它基本上就真的被回收了。

回收方法区

方法区(HotSpot 中的永久代)中的回收是性价比较低的,永久代的垃圾回收主要回收两部分内容:废弃常量和无用的类。对于废弃常量,只要没有任何对象引用常量就会被回收,但是要同时满足以下三个条件才能算是“无用的类”:

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

虚拟机可以对满足上述3个条件的无用类进行回收,是否对类进行回收,HotSpot 虚拟机提供了 -Xnoclassgc 参数进行控制。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值