垃圾收集器知识梳理

92 阅读8分钟

垃圾收集器怎样对待引用类型

垃圾收集器不会回收强引用 类型;

在系统要发生内存溢出异常前,垃圾回收器会将 软引用 回收,若内存仍不够用,则抛出内存溢出异常;

下一次垃圾回收发生时,垃圾回收器会回收弱引用指向的内存;

虚引用 对垃圾回收不产生影响,只是为了使对象在被垃圾回收时会接收到一个通知。

找到存活的对象

安全点:向OopMap存放记录的位置

安全区域:在某一段代码中,引用关系不会发生变化。

记忆集(RememberedSet):用于记录从非收集区域指向收集区域的指针集合的抽象数据结构。用来减少Gc Roots的扫描范围。

卡表:记忆集的一种实现,最简单的实现是字节数组。

卡页:字节数组中的元素,标识着内存中的一块特定大小的内存,被标识的这个内存称作卡页。

如果卡页中的一个或多个对象的字段存在跨代指针,则指向这个内存的元素(也就是字节数组中的一个元素)会标识为1。这样通过检查卡页中为1的元素就可以轻易得出哪些内存块中存在跨代引用,并将其加入GcRoots中一并扫描。

G1 的 记忆集实现:一种HashMap,key是别的region的起始地址,value 是 卡表的索引号。每个Region都有自己的记忆集。

根结点枚举: 查找根结点:活跃线程用户处于安全点时,在OopMap中存放对象的引用。

查找引用链的过程

三色标记的方法。

对象消失的问题存在当切仅当 ,赋值器增加了黑色到白色的引用 且 赋值器删除了所有灰色到白色的引用。

G1按照原始快照的方式避免对象消失的问题。当灰色对象要删除只想白色对象的引用关系时,就要将这个删除的引用记录下来,在并发扫描结束后,再将这些记录过的引用关系中的灰色对象为根,重新扫描一次。

CMS采用增量更新的方式解决对象消失的问题。当黑色对象插入指向白色对象的引用关系时,将这个黑色对象记录下来,等并发扫描结束后,再重新扫描一次这些褐色对象。

垃圾收集发生时如何让用户线程在安全点停下

主动式中断,垃圾收集需要中断线程时,设置一个标志位,用户线程在执行时会轮询这个标志位,一旦标志为真时,就将自己在最近的安全点上主动中断挂起。【通过一条汇编指令test 完成这个功能】

阻塞或者Sleep的线程,在安全区域退出时,会检测垃圾收集器是否完成,未完成则该线程阻塞等待,完成则继续执行。

垃圾收集算法

标记-清除

标记存活的对象,清除未标记的对象。 会产生大量不连续内存碎片。分配大对象时,容易找不到连续的大内存区域,从而提前引发下一次垃圾回收操作。

标记 - 复制

使用 新生代的 Eden 和 一个 Survivor 内存区域分配对象,另一个Survivor 用作垃圾回收时的复制区域。

标记-整理

标记存活的对象,然后将存活的对象都向内存空间的一段移动。

Serial /Serial Old / ParNew /Parallel Scavenage/Parallel Old 垃圾收集器

这些垃圾收集器,在发生垃圾收集时,都会暂停所用的用户线程。

Serial 新生代垃圾收集器,采用标记-复制算法对新生代进行垃圾收集,垃圾回收的线程为单线程模型。

Serial Old 老年代垃圾收集器,采用标记 -整理 算法 对老年代进行回收,垃圾回收的线程为单线程模型。

ParNew ,新生代垃圾收集器,Serial的多线程版本。

Parallel Scavenage ,新生代垃圾收集器,,在 ParNew 的基础上 支持对吞吐量的精确控制。通过控制最大的内存收集停顿时间 以及 直接设置吞吐量的大小来实现。 吞吐量 = 用户代码执行时间/(用户时间+垃圾收集时间)的比例。

Parallel Old, 老年代垃圾收集器, Parallel Scavenage的老年代版本,采用 标记 - 整理 算法 实现。

如何实现根结点枚举 和 可达性分析 以及清除过程

一次性的完成了根结点枚举 和 可达性分析 以及 内存回收的过程。

CMS收集器,老年代的收集器【最短停顿回收垃圾收集器,java9已经标记为弃用】

基于标记 - 清除算法实现,配合新生代的ParNew共同管理

如何实现根结点枚举 和 可达性分析 以及清除过程

初始标记GcRoots直接关联的对象 -> 与用户线程并行,并发的进行遍历对象图的过程(线程数为(处理器核心线程数+3)/4)-> 重新标记(修正第二阶段的变化)-> 与用户线程并行,并发的对未使用的对象进行清除。

(G1)GarbageFisrt垃圾收集器

混合的垃圾收集模式,发生回收时,衡量标准不再是内存属于那个分代,而是判断哪块内存中存放的垃圾最多,回收收益最大。也就是说,Region是垃圾回收的最小单元。允许用户设置内存收集的停顿时间-XX:MaxGcPauseMillils。

G1将内存 划分为 多个大小相等的独立Region 区。

依然保留新生代和老年代的概念,在此基础上增加了一类特殊的Humongous区域【专门存放大对象,判断上是超过region大小的一半,对于超过整个region大小的对象,会为其分配多块连续的Region】。

每个Region根据需要动态扮演新生代和老年代的角色。

如何实现根结点枚举 和 可达性分析 以及清除过程

初始标记仅标记GcRoots能直接关联的对象,并修改TAMS的值。(需要短暂停顿用户线程 ) ->

并发标记,进行可达性分析,这个阶段与用户线程并行,耗时较长。这个过程中分配的对象存放在region的TAMS指针的上方。->

最终标记,短暂暂停用户线程,处理上个阶段新生成的对象引用 ->

筛选回收,更新region的统计数据,对各个region的回收价值和成本进行排序,根据用户设定的期望停顿时间,自由的选择任意多个region构成回收集,然后把决定回收的集合的存活对象复制到空的region中,再清理掉整个旧region的全部空间。这个阶段暂停用户线程,由多个收集器线程并行完成。

ZGC

ZGC 是 一款基于region内存布局,不设分代的,使用了读屏障,染色指针和内存多重映射等技术实现可并发的 标记-整理算法的,以低延迟为目标的一款垃圾收集器。

region是动态创建,销毁,动态分配大小的。

染色指针:Linux 64位机器支持46位的物理地址寻址空间,47位进程虚拟地址空间。ZGC将46位物理寻址空间的高4位提取出来,用作记录对象的三色标记状态(mark0,mark1),是否进入重分配集,是否只能通过finalize方法访问。这样也导致ZGC能管理的内存不能超过4TB。

内存多重映射:把染色指针的高4位看作是分段符,将这些不同的段号映射到同一块物理内存空间。然后经过多重映射转换后,就可以利用染色指针进行正常寻址了。

如何实现根结点枚举 和 可达性分析 以及清除过程

以下各阶段都是和用户线程并行的。

短暂暂停用户线程,对GcRoots直接关联对象进行最初标记,然后经历并发标记完成可达性分析阶段,然后经过最终标记的短暂暂停。【并发标记】

扫描所有的region,得到本次收集过程需要清除的region,并将这些region 组成重分配集。【并发预备重分配】

将重分配集中存活的对象复制到新的region中,并为重分配集中的每个region维护一个转发表,记录从新对象到旧对象的转发关系。 当用户线程并发的访问到了处于重分配集上的对象时,这次访问会被预制的内存屏障所截获,然后立即根据转发表,将访问转发到新复制的对象上,并同时更新该引用的值。【并发重分配】

并发重映射,将修正整个堆中指向重分配集中就对象的所有引用,根据转发表改到新复制的地址。这个阶段在下次垃圾回收器并发标记访问对象图时发生。【并发重映射】