JVM虚拟机学习(3)---JVM垃圾回收

目录页:https://mp.csdn.net/postedit/95937156

1. 小声哔哔

    作为一个Java程序员,我们不需要像C程序员那样对内存释放有过多的关心,但是我觉得还是有必要了解一下JVM的垃圾回收机制。我们知道,GC主要有两种,分别是新生代满的时候触发的Minor GC和老年代满的时候触发的Full GC。

2. GC算法

2.1. 标记-清除算法

    见名知意,标记清除算法就是将算法分为标记和清除两个阶段,将需要回收的对象标记后进行回收。该算法是比较基础的算法,但是存在两个缺点:

  1. 效率不高,标记和回收两个步骤的效率都不高。
  2. 空间问题:标志清除后会产生大量的不连续内存碎片,后续需要分配大对象时会无法找到足够内存空间而触发垃圾回收。

这里说该算法是基础算法的原因是后续的算法都是基于该思路并针对其不足点改进。

2.2. 复制算法

    复制算法的玩法就是将内存分为大小相等的两块,每次只使用一块,需要垃圾回收时,先将存活对象复制到另一块上面,再将已使用的内存空间全部回收。这样每次回收都是对整个半区进行操作,不用考虑内存碎片的情况,只需要移动堆指针按照顺序分配集合。但是代价就是将内存缩小到了原来的一半。

    实际使用中,新生代中的对象98%都是会很快被释放的,并不需要1:1的来进行内存分配,而是将内存分为一块较大的Eden空间和两块较小的Survivor区,每次使用Eden和其中一块Survivor,当回收时将Eden和Survivor中还存活的对象一次性的复制到另一块survivor空间上,最后清理掉Eden和Survivor空间。我们常用的HotSpot虚拟机默认使用的就是这种算法,新生代分为了Eden和两个Survivor区,比例为8:1:1,也就是新生代可用内存为整个新生代的90%。这时问题来了,Survivor空间不足以支撑存活对象复制时就需要依赖老年代进行分配担保。这里的分配担保是指当Survivior区不足时,会有一部分对象被直接复制至老年代。

2.3. 标记整理算法

    标记整理算法的标记过程与标记清除算法一样,只是后续步骤不是直接对可回收对象进行清理,而是让所有对象往一端移动,然后直接处理掉边界外的内存即可。

2.4. 分代收集算法

    这个算法更像是一种对内存的划分逻辑,根据对象的存活周期将内存划分为几块,一般是分为新生代和老年代,这样就可以根据各个年代的特点进行垃圾回收。在新生代,每次垃圾回收时都会发现有大量的对象死去,只有少量存活,就可以选择复制算法。老年代中对象存活率高,且没有多余的内存来给他做分配担保,就需要采用标记清理或标记整理算法。

3. JVM中的垃圾回收器

    垃圾回收器就依赖于各JVM虚拟机的具体实现了,这里只需要了解到垃圾回收器分为单线程,多线程,并行和并发的类型即可,都是开发,单线程多线程就不多说,并行是指垃圾收集的多线程的同时进行。并发:垃圾收集的多线程和应用的多线程同时进行,主要的垃圾回收器如下图所示:

新生代垃圾回收器:

收集器

收集对象和算法

收集器类型

Serial

新生代,复制算法

单线程

ParNew

新生代,复制算法

并行的多线程收集器

Parallel Scavenge

新生代,复制算法

并行的多线程收集器

 

老年代垃圾回收器:

收集器

收集对象和算法

收集器类型

Serial Old

老年代,标记整理算法

单线程

Parallel Old

老年代,标记整理算法

并行的多线程收集器

CMS

老年代,标记清除算法

并行与并发收集器

G1

跨新生代和老年代;标记整理 + 化整为零

并行与并发收集器

3.1. CMS垃圾回收器

    CMS垃圾回收器基于标记清除算法,分为4个步骤:

  1. 初始标记—暂停
  2. 并发标记—同时进行
  3. 重新标记—暂停
  4. 并发清除—同时进行

    初始标记是对GC Root能关联到的对象进行标记,速度很快,重新标记阶段是为了修正并发标记期限因用户程序运作导致标记变动的那一部分对象进行标记,这个阶段的停顿会比初始标记阶段长些,但是比并发标记时间短。

    Concurrent Mark Sweep收集器运行示意图

    由于整个过程暂停时间较短,且并发标记与并发清除可以与用户线程一起工作,所以系统停顿时间短,符合现在大部分网站或B/S系统服务要求,但是其仍存在以下三个问题:

  1. 对CPU敏感:并发阶段,CMS默认启动的线程数是(CPU数量+3)/4,也就是当CPU资源在4个以上时,并发回收时垃圾收集线程会占用不少于25%的CPU资源,但是当CPU资源不足4个时CMS对用户程序的影响就会变的很大,比如2个CPU时会占用50%的CPU资源。
  2. 会产生浮动垃圾,可能出现“Concurrent Mode Failer”失败导致另一次Full GC:由于并发清除阶段用户线程仍在运行,在此阶段有可能会有对象被释放且未被标记,只能下次GC时再进行回收。并发清除阶段还需要预留一部分空间给用户线程使用。
  3. 内存碎片:由于CMS采用的是标记清除算法,会产生内存碎片,导致大对象分配会比较麻烦,为解决这个问题,CMS提供了参数-XX:+UseCMSComactAtFullCollection参数(默认开启),用于在FullGC时开启内存整理,内存整理过程无法与程序并发执行,空间碎片的问题解决了但是会导致停顿时间变长。

可以使用jps -v命令查看JVM启动参数

3.2. G1垃圾回收器

    G1(Garbage-First)收集器是当今收集器技术发展的最前沿结果,JDK1.9以后Oracle公司建议使用该垃圾回收器进行垃圾回 收。

    G1收集器将整个JAVA堆划分为多个大小相等的独立区域,虽然还保留有新生代和老年代的概念,但是已经不再是物理隔离的了,而是一部分独立区域的集合,甚至这部分区域不需要连续。

    除了“化整为零”这一思路,G1还具备以下特点:

  1. 并行与并发: G1利用多CPU,多核环境下的硬件优势,使用多个CPU来缩短stop-the-world的停顿时间。
  2. 分代收集:分代概念在G1中仍得以保留。
  3. 空间整合:G1整体是基于标记整理算法实现的收集器,从局部(两个独立区域)来看是基于复制算法实现,这就意味着不会出现内存碎片,也就不会由于需要分配大对象时获取不到连续的内存空间而提前触发下一次GC
  4. 可预测的停顿:G1可以有计划的避免在整个Java堆中进行全区域的垃圾收集。G1跟踪各个区域中的垃圾堆积的价值大小,在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的区域,既保证的收集效率,又能控制停顿时间。

    G1收集器中,每个区域(Region)都有一个与之对应的Remember Set来避免全堆扫描。G1中每个Region都有一个与之对应的Remember Set,虚拟机发现程序在对Reference类型的数据进行写操作时,会产生一个Write Barrier暂时中断写操作,检查Reference引用的对象是否处于不同的Region之中,如果是就通过CardTable把相关引用信息记录到被应用对象所属的Region的Remember Set中。当进行垃圾回收时,在GC根节点的枚举范围中加入Remember Set即可保证不对全堆扫描也不会有遗漏。

    G1的运作可分为以下几个部分:

1, 初始标记,2.并发标记,3.最终标记,4.筛选回收

4. 思维导图

参考资料:周志明大神-《深入理解Java虚拟机 JVM高级特性与最佳实践》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章