JVM学习记录-对象已死吗

前言

先来回顾一下,在jvm运行时数据区,分为两部分,一个部分是线程共享区,主要包括方法区,另一部是线程私有区分包括本地方法栈虚拟机栈程序计数器。在线程私有部分的三个区域是随着线程生和灭的。栈中的栈帧随着方法的进入和退出而执行着出栈和入栈操作。每一个栈帧所用内存大小在类结构确定下来时就已知了。因此这线程私有区的内存分配和回收都具备确定性,简单概括的说:这部分内存在类加载时分配,在线程结束时回收。(个人理解)

而线程共享区(堆和方法区)则不一样,一个方法中的多个分支需要的内存可能不一样,只有在程序处于运行期间时才能知道会创建哪些对象,这部分的内存的分配和回收都是动态的,垃圾收集器(GC)所关注的也是这部分内存,今天要介绍的内容也是围绕着这部分内存来说的。

对象已死吗

Java世界中几乎所有的对象实例都存放在堆里,垃圾回收器在对堆内存进行回收前,要先确定这些对象中哪些还存活,哪些已死去(死去:不可能再被任何途径使用的对象);

那么如何判断堆内存中的对象是活着还是已经死去了呢?

下面介绍一下主流的两种判断对象是否已经死去算法。

引用计数算法

引用计数算法的思想是:给对象添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减一;任何时刻计数器为0的对象就是不可能再被使用的。

这种算法的特点是实现简单,判定效率也很高,但是Java虚拟机中却没有使用这种算法,主要原因是这种算法很难解决对象之间相互循环引用的问题。

举个?:先看如下代码

public class ReferenceCountingTest {
    
    public Object instance = null;

    public void testGC()
    {
        ReferenceCountingTest objectA = new ReferenceCountingTest();
        ReferenceCountingTest objectB = new ReferenceCountingTest();
        
        objectA.instance = objectB;
        objectB.instance = objectA;
        
        objectA = null;
        objectB = null;
        
        //假设这时发生GC
        System.gc();
        
    }
}

先分析代码,objectA和objectB都被设置成了null,在发生GC的时候应该会回收这样的对象,但是如果按照引用计数算法来看,虽然这两个对象都设置为null了,但是他们还在互相引用,所以各自的计数器都还不是0,所以还不能进行回收。但是这段代码在Java虚拟机下的运行结果是两个对象都已经被回收,这也说明JVM使用的不是引用计数器算法来进行垃圾回收的。

可达性分析算法

在主流的商用程序语言中(Java,C#等)都是通过可达性分析算法来判断对象是否还存活。这个算法的基本思路是通过一组称为“GC Roots”的活跃引用(注意不是对象,和书上写的不一样)作为起始点,从这些活跃的引用开始向下搜索,搜索走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,可以被回收了。

上图中object5、object6、object7、虽然互相有关联,但是它们到GC roots是不可达的所以他们会被判定为可回收的对象。

在Java语言中,可作为GC Roots的对象包括下面几种:

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

再谈引用

JDK1.2之后,Java对引用的概念进行了扩充,将引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference),引用强度依次减弱。

  • 强引用:在程序中普遍存在,类似“Object object = new Object()”;只有强引用存在,对象就不会被回收;
  • 软引用:有用,但不是必需的。在发生内存溢出异常之前,会把这些对象进行二次回收,如果回收了这部分还是内存不够,那就会发生内存溢出。(JDK1.2后引入)
  • 弱引用:也是非必需引用,比软引用更弱一点,弱引用关联的对象只能活到下一次收集之前,只要收集器一工作,无论内存够不够,都会被回收。(JDK1.2后引入)
  • 虚引用:也称为幽灵引用或者幻影引用,为一个对象设置弱引用的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。(JDK1.2后引入)

生存还是死亡

在JVM中,即使在可达性分析算法中不可达的对象,也并非是“非死不可”的,这时候它们暂处于缓刑阶段,一个对象真正死亡,至少要经历两次标记过程;

第一次标记:如果在进行可达性分析以后发现没有与GC Roots相连接的引用链,那它将会被第一次标记并且进行一次筛选,条件是此对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法,或者finalize()方法以及被虚拟机调用过,虚拟机将这两种情况都视为“没有必要执行”。

如果有必要执行,这个对象会被放在一个F_Queue的队列中,并在稍后由一个虚拟机自动建立的、低优先级的Finalizer线程去执行finalize()方法,但不承诺会等它执行结束。

稍后进行第二次标记:如果这时候这个对象重新与引用链上的任何一个对象建立关联就会被移除“即将回收的”的集合,如果这个时候还没有逃脱那基本上就被回收了。

需要注意的是:任何一个对象的finalize()方法都只会被系统自动调用一次。

回收方法区

java虚拟机规范中不要求虚拟机在方法区实现垃圾收集,而且在方法区回收效率很低。

方法区注意回收两部分内容:废弃常量和无用的类

判定一个常量比较简单(一个常量任何地方都没有被引用了,就可以认为是废弃常量了),而判定一个类是否是无用类,就相对比较苛刻,需要满足以下三个条件,才能被认定为是一个无用的类。

  • 该类所有的实例都已经被回收。
  • 加载该类的ClassLoader已经被回收。
  • 该类对应的java.lang.Class对象没有在任何地方被引用,无法再任何地方通过反射访问该类的方法。

在大量使用反射、动态代理、Cglib等ByteCode框架、动态生成JSP以及OSGi这类频繁自定义ClassLoader的场景,都是需要进行无用类回收的。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏MasiMaro 的技术博文

C++ 调用Python3

作为一种胶水语言,Python 能够很容易地调用 C 、 C++ 等语言,也能够通过其他语言调用 Python 的模块。

63650
来自专栏刘望舒

Java虚拟机(三)垃圾标记算法与Java对象的生命周期

前言 这一节我们来简单的介绍垃圾收集器,并学习垃圾标记的算法:引用计数算法和根搜索算法,为了更好的理解根搜索算法,会在文章的最后介绍Java对象在虚拟机中的...

20360
来自专栏个人分享

JAVA 几种引用类型学习

1、对象的强、软、弱和虚引用     在JDK 1.2以前的版本中,若一个对象不被任何变量引用,那么程序就无法再使用这个对象。也就是说,只有对象处于可触及(re...

37220
来自专栏互联网大杂烩

腾讯面试

时间:2017年3月5号 这次面试和之前的面试差不多,一开始聊项目。聊了项目之后就问基础了,项目的话就不说了。

8620
来自专栏Ryan Miao

java基础面试题

参考:http://blog.csdn.net/jackfrued/article/details/44921941 说未经允许不转载,我只好参考了。 1.面向...

38050
来自专栏cmazxiaoma的架构师之路

关于牛客网的JAVA面试题错题总结以及归纳(1)

15730
来自专栏Python中文社区

Python源码分析:类的机制

作者:小屋子大侠,目前主要从事于python后端相关工作,使用使用python大概2年半的时间,平常喜欢分析工作中使用的工具的源码,如supervisor,gu...

41220
来自专栏Jimoer

在Java的反射中,Class.forName和ClassLoader的区别

最近在面试过程中有被问到,在Java反射中Class.forName()加载类和使用ClassLoader加载类的区别。当时没有想出来后来自己研究了一下就写下来...

19520
来自专栏java初学

java — 垃圾回收

433100
来自专栏空帆船w

Java的强引用,软引用,弱引用,虚引用及其使用场景

从 JDK1.2 版本开始,Java 把对象的引用分为四种级别,从而使程序能更加灵活的控制对象的生命周期。这四种级别由高到低依次为:强引用、软引用、弱引用和虚引...

39520

扫码关注云+社区

领取腾讯云代金券