Java GC 垃圾清理算法和常见的垃圾回收器

什么是垃圾

一般来说没有引用指向的就是垃圾,但是有引用指向的也有可能是垃圾。

如何找到垃圾

引用计数(Reference Count)

对每个对象的被引用个数进行计数。若有三个引用指向它,则它的引用计数为3。

若为0,则说明它是垃圾。

缺点:无法处理循环引用的垃圾。如下图所示

1
1
1

根可达算法(Root Searching)

通过一系列的“GC roots”对象作为起点搜索。如果在“GC roots”和一个对象之间没有可达路径,则称该对象是不可达的

要注意的是,不可达对象不等价于可回收对象,不可达对象变为可回收对象至少要经过两次标记过程。两次标记后仍然是可回收对象,则将面临回收。

如何清理垃圾

标记清除 (Mark-Sweep)

最基础的垃圾回收算法,分为两个阶段,标注和清除。标记阶段标记出所有需要回收的对象,清除阶段回收被标记的对象所占用的空间。
在这里插入图片描述

缺点:内存碎片化严重

复制 (Copying)

按内存容量将内存划分为等大小的两块。每次只使用其中一块,当这一块内存满后将尚存活的对象复制到另一块上去,把已使用的内存清掉

在这里插入图片描述

优点:实现简单,内存效率高,不易产生碎片

缺点:可用内存被压缩到了原本的一半。如果存活对象增多,Copying算法的效率会大大降低

标记压缩 (Mark-Compact)

标记阶段和Mark-Sweep算法相同,标记后不是清理对象,而是将存活对象移向内存的一端,然后清除端边界外的对象。

在这里插入图片描述

唯一问题是效率稍微低一些。

JVM分代算法

分代收集法是目前大部分JVM所采用的方法,其核心思想是根据对象存活的不同生命周期将内存划分为不同的域,一般情况下将GC堆划分为老生代(Tenured/Old Generation)新生代(Young Generation)

老生代的特点是每次垃圾回收时只有少量对象需要被回收,新生代的特点是每次垃圾回收时都有大量垃圾需要被回收,因此可以根据不同区域选择不同的算法。

新生代

目前大部分JVM的GC对于新生代都采取Copying算法

因为新生代中每次垃圾回收都要回收大部分对象,即要复制的操作比较少,但通常并不是按照1:1来划分新生代。一般将新生代划分为一块较大的Eden空间和两个较小的Survivor空间(From Space, To Space)。

每次使用Eden空间和其中的一块Survivor空间,当进行回收时,将该两块空间中还存活的对象复制到另一块Survivor空间中。

在这里插入图片描述

老年代

而老年代因为每次只回收少量对象,因而采用Mark-Compact算法。

  1. JAVA虚拟机提到过的处于方法区的永生代(Permanent Generation),它用来存储class类,常量,方法描述等。对永生代的回收主要包括废弃常量和无用的类。
  2. 对象的内存分配主要在新生代的Eden Space和Survivor Space的From Space(Survivor目前存放对象的那一块),少数情况会直接分配到老生代。
  3. 当新生代的Eden Space和From Space空间不足时就会发生一次GC,进行GC后,Eden Space和From Space区的存活对象会被挪到To Space,然后将Eden Space和From Space进行清理。
  4. 如果To Space无法足够存储某个对象,则将这个对象存储到老生代。
  5. 在进行GC后,使用的便是Eden Space和To Space了,如此反复循环。
  6. 当对象在Survivor区躲过一次GC后,其年龄就会+1。默认情况下年龄到达15的对象会被移到老生代中

垃圾回收器种类

在这里插入图片描述

常用组合:

  • Serial + Serial Old

  • Parallel Scavenge + Parallel Old

  • ParNew + CMS

Java 1.3

Serial:串行

Serial(年轻代)

关键词:复制 + 单线程

a stop-the-world (STW), copying collector which uses a single GC thread.

在这里插入图片描述

Serial Old(老年代)

关键词:标记压缩 + 单线程

a stop-the-world, mark-sweep-compact collector that uses a single GC thread

在这里插入图片描述

Java 1.8

Parallel:并行

Parallel Scavenge(年轻代)

关键词:复制 + 多线程

a stop-the-world, copying collector which uses multiple GC threads.

在这里插入图片描述

Parallel Old(老年代)

关键词:标记压缩 + 多线程

a compacting collector that uses multiple GC threads.

在这里插入图片描述

特殊

ParNew(年轻代)

关键词:复制算法 + 多线程

为了配合CMS设计而出。基本上和Parallel Scavenge算法一样。

  • a stop-the-world, copying collector which uses multiple GC threads.
  • It differs from “Parallel Scavenge" in that it has enhancements that make it usable with CMS。
  • For example, “ParNew” does the synchronization needed so that it can run during the concurrent phases of CMS.

在这里插入图片描述

CMS(老年代)

关键词:标记清除算法 + 多线程 + 并发

  • concurrent(并发)mark-sweep
  • a mostly concurrent, low-pause(暂停时间短) collector
  • 四个阶段
    1. 初始标记 initial mark:时间非常短,将垃圾的根部标记
    2. 并发标记 concurrent mark:时间最长,根据标记的根部并发标记垃圾
    3. 重新标记 remark:由于是并发标记,因此可能会有新的垃圾产生或者标记的垃圾不再是垃圾,所以需要重新标记
    4. 并发清理 concurrent sweep:清理标记好的垃圾即可

在这里插入图片描述

缺点:

  1. 内存碎片化严重
  2. 会产生浮动垃圾(并发清理时产生的新的垃圾,下次垃圾回收时回收)

G1 (Garbage-First)

  1. 空间整合:基于标记-压缩算法,不产生内存碎片。
  2. 可预测停顿:可以非常精确控制停顿时间,在不牺牲吞吐量前提下,实现低停顿垃圾回收。
  3. 使用G1收集器时,Java堆的内存布局与其他收集器有很大的区别,它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留着新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,他们都是一部分 Region(不需要连续)的集合
  4. G1收集器避免全区域垃圾收集,它把堆内存划分为大小固定的几个独立区域(Region),并且跟踪这些区域的垃圾收集进度,同时在后台维护一个优先级列表,每次根据所允许的收集时间,优先回收垃圾最多的区域。区域划分和优先级区域回收机制,确保G1收集器可以在有限时间获得最高的垃圾收集效率。

ZGC

染色指针

HotSpot虚拟机的标记实现方案有如下几种:

  • 把标记直接记录在对象头上(如 Serial收集器)
  • 把标记记录在与对象相互独立的数据结构上(如G1、Shenandoah使用了一种相当于堆内存的1/64大小的,称为BitMap的结构来记录标记信息)
  • 目前在Linux下64位的操作系统中高18位是不能用来寻址的,但是剩余的46为却可以支持64T的空间,到目前为止我们几乎还用不到这么多内存。于是ZGC将46位中的高4位取出,用来存储4个标志位,剩余的42位可以支持4T的内存。
三色标记

在并发的可达性分析算法中我们使用三色标记(Tri-color Marking)来标记对象是否被收集器访问过

  • 白色:表示对象尚未被垃圾收集器访问过。显然在可达性分析刚刚开始的阶段,所有的对象都是白色的,若在分析结束的阶段,仍然是白色的对象,即代表不可达
  • 黑色:表示对象已经被垃圾收集器访问过,且这个对象的所有引用都已经扫描过。黑色的对象代表已经扫描过,它是安全存活的,如果有其他对象引用指向了黑色对象,无须重新扫描一遍。黑色对象不可能直接(不经过灰色对象)指向某个白色对象。
  • 灰色:表示对象已经被垃圾收集器访问过,但这个对象上至少存在一个引用还没有被扫描过。

标记步骤:

  1. 初始时,所有对象都在 【白色集合】中;
  2. 将GC Roots 直接引用到的对象 挪到 【灰色集合】中;
  3. 从灰色集合中获取对象:
    3.1. 将本对象 引用到的 其他对象 全部挪到 【灰色集合】中;
    3.2. 将本对象 挪到 【黑色集合】里面。
  4. 重复步骤3,直至【灰色集合】为空时结束。
  5. 结束后,仍在【白色集合】的对象即为GC Roots 不可达,可以进行回收。
读写屏障
  • 当对象从堆中加载的时候,就会使用到读屏障(Load Barrier)。这里使用读屏障的主要作用就是检查指针上的三色标记位,根据标记位判断出对象是否被移动过,如果没有可以直接访问,如果移动过就需要进行“自愈”(对象访问会变慢,但也只会有一次变慢),当“自愈”完成后,后续访问就不会变慢。
  • 读写屏障可以理解成对象访问的“AOP”操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值