关于jvm虚拟机:可达性分析算法与Java引用类型

28次阅读

共计 3806 个字符,预计需要花费 10 分钟才能阅读完成。

垃圾收集概述

垃圾收集(Garbage Collection,下文简称 GC)能够了解为无用内存的回收,1960 年诞生的 Lisp 语言的作者 John McCarthy 就思考过垃圾收集须要实现的三件事件:

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

为什么咱们还要去理解垃圾收集和内存调配?答案很简略:当须要排查各种内存溢出、内存透露问题时,当垃圾收集成为零碎达到更高并发量的瓶颈时,咱们就必须对这些“自动化”的技术施行必要的监控和调节。

哪些内存须要回收?

Java 内存运行时区域中的 程序计数器、虚拟机栈、本地办法栈 3 个区域随线程而生,随线程而灭。每一个栈帧中调配多少内存基本上是在类构造确定下来时就已知的,因而这几个区域的内存调配和回收都具备确定性,不须要过多思考如何回收的问题,当办法完结或者线程完结时,内存天然就跟随着回收了。

Java 堆和办法区这两个区域的内存空间是线程共享的,在程序运行期间始终存在 ,并且有限度最大占用空间的参数设置,所以须要对这两局部的内存空间进行回收利用, 垃圾收集器所关注的正是这部分内存该如何治理

在堆中存着简直所有的对象实例,垃圾回收器在对堆进行回收前,就须要先 判断堆中的对象哪些还“存活”着,哪些曾经“死去”(“死去”即不可能再被任何路径应用的对象)

援用计数算法

判断内存中对象是否是垃圾的算法有很多,“援用计数法”是其中一种,其判断对象是否存活的算法如下:

  1. 在对象中增加一个援用计数器
  2. 每当有一个中央援用它时,计数器值就加一
  3. 当援用生效时,计数器值就减一
  4. 任何时刻计数器为零的对象就是不可能再被应用的。

主观地说,援用计数算法(Reference Counting)尽管占用了一些额定的内存空间来进行计数,但它的原理简略,断定效率也很高,在大多数状况下它都是一个不错的算法。也有一些比拟驰名的利用案例,应用了援用计数算法进行内存治理,例如:

  • 微软 COM(Component Object Model)技术
  • 应用 ActionScript 3 的 FlashPlayer
  • Python 语言
  • 在游戏脚本畛域失去许多利用的 Squirrel 中。

但在 Java 畛域,至多 支流的 Java 虚拟机外面都没有选用援用计数算法来治理内存 ,次要起因是,这个看似简略的算法有很多例外情况要思考,必须要配合大量额定解决能力保障正确地工作,譬如单纯的援用计数就很难解决 对象之间互相循环援用的问题

可达性剖析算法

以后支流的商用程序语言(Java、C#,上溯至后面提到的古老的 Lisp)的内存管理子系统,都是通过 可达性剖析(Reachability Analysis)算法 来断定对象是否存活的。这个算法的基本思路就是:

  1. 通过一系列称为“GC Roots”的根对象作为起始节点集
  2. 从这些节点开始,依据援用关系向下搜寻,搜寻过程所走过的门路称为“援用链”(Reference Chain)
  3. 如果某个对象到 GC Roots 间没有任何援用链相连,或者用图论的话来说就是从 GC Roots 到这个对象不可达时,则证实此对象是不可能再被应用的。

如图下图,对象 object 5、object 6、object 7 尽管互有关联,然而它们到 GC Roots 是不可达的,因而它们将会被断定为可回收的对象。

在 Java 技术体系外面,固定可作为 GC Roots 的对象 包含以下几种:

  • 在虚拟机栈(栈帧中的本地变量表)中援用的对象,譬如各个线程被调用的办法堆栈中应用到的参数、局部变量、长期变量等。
  • 在办法区中类动态属性援用的对象,譬如 Java 类的援用类型动态变量。
  • 在办法区中常量援用的对象,譬如字符串常量池(String Table)里的援用。
  • 在本地办法栈中 JNI(即通常所说的 Native 办法)援用的对象。
  • Java 虚拟机外部的援用,如根本数据类型对应的 Class 对象,一些常驻的异样对象(比方 NullPointExcepiton、OutOfMemoryError)等,还有零碎类加载器。
  • 所有被同步锁(synchronized 关键字)持有的对象。
  • 反映 Java 虚拟机外部状况的 JMXBean、JVMTI 中注册的回调、本地代码缓存等。

除了固定的 GC Roots 汇合以外,还能够有其余对象“临时性”地退出,独特形成残缺 GC Roots 汇合(依据用户所选用的垃圾收集器以及以后回收的内存区域不同)。比方只针对 Java 堆中某一块区域发动垃圾收集时(如最典型的只针对新生代的垃圾收集),这个区域里的对象齐全有可能被位于堆中其余区域的对象所援用,这时候就须要将这些关联区域的对象也一并退出 GC Roots 汇合中去,能力保障可达性剖析的正确性。

Java 中的援用

无论是通过援用计数算法判断对象的援用数量,还是通过可达性剖析算法判断对象是否援用链可达,断定对象是否存活都和“援用”离不开关系。

在 JDK 1.2 版之前,Java 外面的援用是很传统的定义 :如果 reference 类型的数据中存储的数值代表的是另外一块内存的起始地址,就称该 reference 数据是代表某块内存、某个对象的援用。一个对象在这种定义下 只有“被援用”或者“未被援用”两种状态,对于形容一些“食之无味,弃之可惜”的对象就显得无能为力了。譬如咱们心愿能形容一类对象:当内存空间还足够时,能保留在内存之中,如果内存空间在进行垃圾收集后依然十分缓和,那就能够摈弃这些对象——很多零碎的缓存性能都合乎这样的利用场景。

在 JDK 1.2 版之后,Java 对援用的概念进行了裁减,将援用分为 强援用(Strongly Re-ference)、软援用(Soft Reference)、弱援用(Weak Reference)和虚援用(Phantom Reference)4 种,这 4 种援用强度顺次逐步削弱

强援用

强援用是最传统的“援用”的定义,是指在程序代码之中普遍存在的援用赋值,即相似“Object obj=new Object()”这种援用关系。无论任何状况下,只有强援用关系还存在,垃圾收集器就永远不会回收掉被援用的对象。

软援用

软援用是用来形容一些还有用,但非必须的对象。只被软援用关联着的对象,在零碎将要产生内存溢出异样前,会把这些对象列进回收范畴之中进行第二次回收 ,如果这次回收还没有足够的内存,才会抛出内存溢出异样。在 JDK 1.2 版之后提供了SoftReference 类 来实现软援用。

弱援用

弱援用也是用来形容那些非必须对象,然而它的强度比软援用更弱一些,被弱援用关联的对象只能生存到下一次垃圾收集产生为止 。当垃圾收集器开始工作,无论以后内存是否足够,都会回收掉只被弱援用关联的对象。在 JDK 1.2 版之后提供了WeakReference 类 来实现弱援用。

虚援用

虚援用也称为“幽灵援用”或者“幻影援用”,它是最弱的一种援用关系。一个对象是否有虚援用的存在,齐全不会对其生存工夫形成影响,也无奈通过虚援用来获得一个对象实例。为一个对象设置虚援用关联的惟一目标只是为了能在这个对象被收集器回收时收到一个零碎告诉 。在 JDK 1.2 版之后提供了PhantomReference 类 来实现虚援用。

集体总结

垃圾收集器所关注的内存区域是哪局部?

  • 程序计数器、虚拟机栈、本地办法栈 3 个区域随线程而生,随线程而灭,当办法完结或者线程完结时,内存天然就跟随着回收了,因而不须要过多思考如何回收的问题。
  • Java 堆和办法区这两个区域则有着很显著的不确定性,这部分内存的调配和回收是动静的,垃圾收集器所关注的正是这部分内存该如何治理。

常见的判断对象是否存活的算法有哪些?断定过程是怎么?

  • 援用计数算法

    1. 在对象中增加一个援用计数器,每当有一个中央援用它时,计数器值就加一;当援用生效时,计数器值就减一;
    2. 任何时刻计数器为零的对象就是不可能再被应用的。
  • 可达性剖析算法

    1. 通过一系列称为“GC Roots”的根对象作为起始节点集
    2. 从这些节点开始,依据援用关系向下搜寻,搜寻过程所走过的门路称为“援用链”(Reference Chain)
    3. 如果某个对象到 GC Roots 间没有任何援用链相连,即 GC Roots 到这个对象不可达时,则证实此对象是不可能再被应用的。

Java 为什么没有抉择援用计数法来判断对象是否“存活”?

  • 援用计数算法尽管占用了一些额定的内存空间来进行计数,但它的原理简略,断定效率也很高,在大多数状况下它都是一个不错的算法。
  • 援用计数法看似简略,却要思考很多例外情况,譬如对象之间的循环援用问题。

JDK1.2 后 Java 中的援用分为哪几种?

  1. 强援用。是指在程序代码之中普遍存在的援用赋值,即相似“Object obj=new Object()”这种援用关系。无论任何状况下,只有强援用关系还存在,垃圾收集器就永远不会回收掉被援用的对象。
  2. 软援用。用来形容一些还有用,但非必须的对象。在零碎将要产生内存溢出异样前,会把这些对象列进回收范畴之中进行第二次回收,如果这次回收还没有足够的内存,才会抛出内存溢出异样。提供了 SoftReference 类来实现软援用。
  3. 弱援用。用来形容那些非必须对象,然而它的强度比软援用更弱一些。被弱援用关联的对象只能生存到下一次垃圾收集产生为止。提供了 WeakReference 类来实现弱援用。
  4. 虚援用。也称为“幽灵援用”或者“幻影援用”,它是最弱的一种援用关系。无奈通过虚援用来获得一个对象实例,惟一目标只是为了能在这个对象被收集器回收时收到一个零碎告诉。提供了 PhantomReference 类来实现虚援用。

参考资料

  • 《深刻了解 Java 虚拟机第三版》
正文完
 0