《深入理解java虚拟机——JVM高级特性与最佳实践》阅读笔记 垃圾回收算法

主流 JVM 使用的垃圾回收算法并不是引用计数法,因为该方法的一个致命缺陷在于两个废弃对象间互相的无效引用会导致无法回收JVM常用算法可达性分析算法
该方法原理在于通过从“ GC Root节点往下搜索,对于搜索可到达的对象,判定为有用;不可到达对象判定为需要回收。可作为 GC Root 的对象一共有4种:

序号 对象
1 虚拟机栈中引用的对象
2 方法区中类静态属性引用的对象
3 方法区中常量引用的对象
4 Native 方法引用的对象

引用的概念,一共有4个级别:

名称 含义
强引用 代码中 “Object o = new Object();” 这类语句。只要这类语句存在,永远不会被回收
软引用 对象依然有用,但并非必须。在发生内存溢出之前会进行二次回收
弱引用 对象有用,但比级别比软引用对象低。在垃圾回收发生时会被强制清除
虚引用 对对象的引用并没有实际价值,只是为了在对象被回收时收到系统通知

在可达性分析算法中不可到达的对象并不是一定会被回收,还需要经过两次标记过程:

步骤 内容
1 被算法分析过不可到达后,会被进行第一次标记
2 第一次标记后开始进行筛选,条件是是否有必要执行对象的 finalize() 方法

如果对象没有覆盖finalize()方法,或者已经被虚拟机调用过,则判定为不需要执行
若判定需要执行回收,则执行以下过程:

步骤 内容
1 将对象放置于 F-Queue 队列中,之后由虚拟机自动建立的低优先级的 Finalizer 线程执行
2 GC 对 F-Queue 中对象进行二次标记,这一步就会进行真正的回收处理

步骤1中 Finalizer 线程的执行是指,虚拟机会触发方法,但由于可能出现对象执行缓慢、死循环等问题导致队列中其它对象永久等待甚至是回收系统崩溃的问题,因此不会保证等待其执行结束。
对象逃离第二次标记的方法是,重新与引用链中的任何一个对象建立连接
建议尽量使用 try-finally 替代 finalize() 方法(该方法运行代价高昂,无法保证对象调用顺序)

可达性分析算法的时间消耗主要有两点:

序号 内容
1 可作为 GC Root 的节点主要在全局性引用与执行上下文中,搜索全部节点需要耗费大量时间
2 因为分析工作必须在能够确保一致性(执行系统被“冻结”)的快照中进行,由此导致 GC 必须停止所有 java 线程

目前主流虚拟机使用的是准确式GC,执行系统在停止后不需要彻底检查执行上下文以及全局引用位置。
以 HotSpot 为例,其使用名为 OopMap的数据结构达成该目的。类加载完成时,HotSpot 就将对象内偏移量上相应的数据类型计算出来,并且在JTL编译时,也会在特定位置记录下栈和寄存器中的引用。由此,GC在扫描时可直接获得信息。

商用虚拟机使用分代回收算法,将 java 堆内存划分为新生代老年代。新生代在垃圾回收时有大量对象死亡,采用复制算法;老年代存活率高,没有空间能够进行担保,需要使用标记—清理标记—整理算法

复制算法,思想是将内存分为两等份,一份空置,另一份使用;清理时,将使用的空间中存活的对象复制进空置空间中,再清除整个使用空间。由于存在较大的内存浪费,所以实际设计虚拟机时,将内存分为三份:最大的 Eden区 ,两份较小的等大小的 Survivor区

HotSpot 中,Survivor 与 Eden 的空间比默认为 1:8 ,每次使用的空间为一块 Survivor 区以及 Eden 区。也就是使用空间可达 90% 。回收时,将存活对象复制到另一块 Survivor 区中,再清空原区域。

这种模式保证了大多数场景下能够最大化利用内存,但是无法解决存活对象过多,所需空间超过内存10%的情况。此时,需要依赖老年代为其进行分配担保。也就是,当空间不足时,存活对象将直接进入老年代空间进行存储

标记—整理算法,由于老年代无法寻求空间进行分配担保,因此不可使用复制算法那种非常浪费空间的清理方式。取而代之的是,将存活对象进行标记,收集时将对象全部移动到内存的一端,再清空另一端的空间。

你可能感兴趣的:(阅读笔记)