3.2.2 可达性分析算法
当前主流的商用程序语言(Java、 C#,上溯至前面提到的古老的 Lisp)的内存管理子系统,都是通过可达性分析( Reachability Analysis)算 法来判定对象是否存活的。这个算法的基本思路就是通过一系列称为 “GC Roots”的根对象作为起始节点集,从这些节点开始,根据 引用关系向下搜索,搜索过程所走过的路径称为 “引用链 Reference Chain),如果某个对象到),如果某个对象到GC Roots间没有任何引用链相连,或者用图论的话来说就是间没有任何引用链相连,或者用图论的话来说就是从从GC Roots到这个对象不可达时,则证明此对象是不可能再被使用的。到这个对象不可达时,则证明此对象是不可能再被使用的。
如图3-1所示,对象 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
3.2.3 再谈引用
在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.2.5 回收方法区
虚拟机规范》中提到过可以不要求虚拟机在方法区中实现垃圾收集,事实上也 确实有未实现或未能完整实现方法区类型卸载的收集器存在(如 JDK 11时期的ZGC收集器就不支持类卸载),方法区垃圾收集的 “性价比 ”通常也是比较低的:在 Java堆中,尤其是在新生代中,对常规应用进行一次垃圾收集通常可以回收 70%至 99%的内存空间,相比之下,方法区回收囿于苛刻的判定条件,其区域垃圾收集的回收成果往往远低于此。
方法区的垃圾收集主要回收两部分内容:废弃的常量和不再使用的类型。回收废弃常量与回收 Java堆中的对象非常类似。在大量使用反射、动态代理、 CGLib等字节码框架,动态生成 JSP以及 OSGi这类频繁自定义类加载器的场景中,通常都需要 Java虚拟机具备类型卸载的能力,以保证不会对方法区 造成过大的内存压力。
3.3 垃圾收集算法
从如何判定对象消亡的角度出发,垃圾收集算法可以划分为“引用计数式垃圾收集 ”Reference Counting GC)和 “追踪式垃圾收集 Tracing GC)两大类,这两类也常被称作 “直接垃圾收集 ”和 “间接垃圾收集 ”。 由于引用计数式垃圾收集算法在本书讨论到的主流 Java虚拟机中均未涉及,所以我们暂不把它作为正文主要内容来讲解,本节介绍的所有算法均属于追踪式垃圾收集的范畴。】
3.3.1 分代收集理论
当前商业虚拟机的垃圾收集器,大多数都遵循了 “分代收集 Generational Collection))[1]的理论进行设计,分代收集名为理论,实质是一套符合大多数程序运行实际情况的经验法则,它建立在两个分代假说之上:
- 弱分代假说(Weak Generational Hypothesis):绝大多数对象都是朝生夕灭的。
- 强分代假说(Strong Generational Hypothesis):熬过越多次垃圾收集过程的对象就越难以消亡。
这两个分代假说共同奠定了多款常用的垃圾收集器的一致的设计原则:收集器应该将 Java堆划分出不同的区域,然后 将回收对象依据其年龄(年 龄即对象熬过垃圾收集过程的次数)分配到不同的区域之中存储。显而易见,如果一个区域中大多数对象都是朝生夕灭,难以熬过垃圾收集过程的话,那么把它们集中放在一起,每次回收时只关注如何保留少量存活而不是去标记那些大量将要被回收的对象,就能以较低代价回收到大量的空间;如果剩下的都是难以消亡的对象,那把它们集中放在一块,虚拟机便可以使用较低的频率来回收这个区域,这就同时兼顾了垃圾收集的时间开销和内存的空间有效利用。
在Java堆划分出不同的区域之后,垃圾收集器才可以每次只 回收其中某一个或者某些部 分的区域 ——因而才有了 “Minor GC”“Major GC”“Full GC”这样的回收类型的划分;也才能够针对不同的区域安排与里面存储对象存亡特征相匹配的垃圾收集算法 ——因而发展出了 “标记 -复制算法 ”“标记 -清除算法 ”“标记 -整理算法 ”等针对性的垃圾收集算法。
3.跨代引用假说 Intergenerational Reference Hypothesis):跨代引用相对于同代引用来说仅占极少数。 这其实是可根据前两条假说逻辑推理得出的隐含推论:存在互相引用关系的两个对象,是应该倾向于同时生存或者同时消亡的。举个例子,如果某个新生代对象存在跨代引用,由于老 年代对象难以消亡,该引用 会使得新生代对象在收集时 同样得以存活,进而在年龄增长之后晋升到老年代中,这时跨代引用也随即被消除了。 依据这条假说,我们就不应再为了少量的跨代引用去扫描整个老年代,也不必浪费空间专门记录每一个对象是否存在及存在哪些跨代引用,只需在新生代上建立一个全局的数据结构(该结构被称为 “记忆集 Remembered Set),这个结构把老年代划分成若干小块,标识出老年代的哪一块内存会存在跨代引用。此后当发生 Minor GC时,只有包含了跨代引用的小块内存里的对象才会被加入到包含了跨代引用的小块内存里的对象才会被加入到GCRoots进行扫描。虽然这种方法需进行扫描。虽然这种方法需要在对要在对象改变引用关系(如将自己或者某象改变引用关系(如将自己或者某个属性赋值)时维护记录数据的正确性,会增个属性赋值)时维护记录数据的正确性,会增加一些运行时的开销,但比起收集时扫描整个老年代来说仍然是划算的。加一些运行时的开销,但比起收集时扫描整个老年代来说仍然是划算的。 算法分为标记清除算法,标记复制算法,标记整理算法。3.4 HotSpot的算法细节实现
根节点枚举,安全点,安全区域,记忆集与卡表,写屏障,并发的可达性分析标签:java,收集,收集器,对象,虚拟机,回收,垃圾,引用 From: https://www.cnblogs.com/yuluoxingkong/p/18393092