JVM——GC(垃圾回收机制)

  堆是Java虚拟机所管理的内存中最大的一块存储区域。堆内存被所有线程共享。主要存放使用new关键字创建的对象。所有对象实例以及数组都要在堆上分配。垃圾收集器就是根据GC算法,收集堆上对象所占用的内存空间(收集的是对象占用的空间而不是对象本身)。

  • Java堆分为新生代(Young Generation)和老年代(Old Generation);
  • 年轻代又分为伊甸园(Eden)和幸存区(Survivor);
  • 幸存区又分为From Survivor-S0空间和 To Survivor-S1空间。
  • 新生代存储“新生对象”,我们新创建的对象存储在新生代中。当年轻内存占满后,会触发Minor GC,清理新生代内存空间。
  • 老年代存储长期存活的对象大对象。新生代中存储的对象,经过多次GC后仍然存活的对象会移动到老年代中进行存储。老年代空间占满后,会触发Full GC
  • 通常 S0:S1:Eden = 1:1:8

:Full GC是清理整个堆空间,包括新生代和老年代。如果Full GC之后,堆中仍然无法存储对象,就会抛出OutOfMemoryError异常。

垃圾收集器

Stop the World机制,简称STW,即在执行垃圾收集算法时,Java应用程序中除垃圾收集器线程之外的线程都被挂起。

垃圾收集算法

可达性算法(判断一个对象是否存活)

通过一些称之为“GC Roots” 的对象作为起点;从此起点向下搜索,所走过的路径称之为引用链,当一个对象到 GC Roots 没有任何引用链相连接,代表此对象不可达。

Java 可以作为GC Roots 的对象包括:

  • 虚拟机栈(帧栈中的本地变量表)中的引用对象。
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI (即一般说的 Native 方法) 的引用对象

此外还有引用计数法,但是会有循环引用的问题。

常见的垃圾收集器

在这里插入图片描述

  • Serial: (串行)收集器是最基本、历史最悠久的垃圾收集器,是单线程收集器,进行收集时其他线程需要STW。(新生代收集器)
  • ParNew: Serial的多线程版本(新生代收集器)
  • Parallel Scavenge: 关注是吞吐量(cpu中用于运行用户代码的时间与cpu总消耗的时间比值)(新生代收集器)
  • Serial Old: Serial的老年代版本.它同样是一个单线程收集器(老年代收集器)
  • Parallel Old: Parallel Scavenge收集器的老年代版本,多线程与标记-整理算法(老年代收集器)
  • CMS: (Concurrent Mark SweepHotspot)虚拟机第一款真正意义上的并发收集器,让垃圾收集线程与用户线程(基本上)同时工作 (老年代收集器)
  • G1: (Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征

常见组合

  • Serial/Parallel Scavenge + Serial Old
  • ParNew + CMS:对大内存的效率最高
  • Parallel Scavenge + Parallel Old:JDK1.8默认使用

CMS特性

CMS收集器采用标记-清除算法,过程分为四个步骤

  • 初始标记
    (STW)暂停所有的其它线程,并记录下直接与root相连的对象,速度很快
  • 并发标记
    同时开启GC和用户线程,用一个闭包结构去记录可达对象。
    但在这个阶段结束,这个闭包结构并不能保证包含当前所有的可达对象。
    因为用户线程可能会不断的更新引用域,所以GC线程无法保证可达性分析的实时性。
    所以这个算法里会跟踪记录这些发生引用更新的地方。
  • 重新标记
    (STW)重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变动的那一部分对象的标记记录。这个阶段的停顿时间一般会比初始标记阶段的时间稍长,远远比并发标记阶段时间短。运用三色标记算法
  • 并发清除
    开启用户线程,同时GC线程开始对为标记的区域做清扫。
  • 并发重置,重置三色标记,将对象全置为白色。

在这里插入图片描述

其中并发标记和并发清除相对于其他两个步骤,操作时间很慢,是垃圾回收效率的瓶颈点。

:标记-清除算法会有内存碎片化的问题,可通过设置CMSFullGCsBeforeCompaction,每隔多少次不压缩的Full GC后,执行一次带压缩的Full GC。默认值为0,表示每次进入Full GC时都进行碎片整理。

几个明显的缺点:

  • 对CPU资源敏感(会和服务抢资源);
  • 无法处理浮动垃圾(在并发标记和并发清理阶段又产生垃圾,这种浮动垃圾只能等到下一次gc再清理了);
  • 它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生,当然通过参数-XX:+UseCMSCompactAtFullCollection可以让jvm在执行完标记清除后再做整理
  • 执行过程中的不确定性,会存在上一次垃圾回收还没执行完,然后垃圾回收又被触发的情况,特别是在并发标记和并发清理阶段会出现,一边回收,系统一边运行,也许没回收完就再次触发full gc,也就是"concurrent mode failure",此时会进入stop the world,用serial old垃圾收集器来回收。一般通过配置,-XX:CMSInitiatingOccupancyFraction: 当老年代使用达到该比例时会触发FullGC(默认是92,这是百分比),适当降低数值来保证在Full GC时老年代有足够的内存。

G1

在这里插入图片描述

G1将堆内存划分成若干个(默认2048)区域,即 Region
G1不再有物理分代,但是还是逻辑分代。
G1收集器一次GC(主要值Mixed GC)的运作过程大致分为以下几个步骤:

  • 初始标记(initial mark,STW):暂停所有的其他线程,并记录下gc roots直接能引用的对象,速度很快 ;
  • 并发标记(Concurrent Marking):同CMS的并发标记
  • 最终标记(Remark,STW):同CMS的重新标记
  • 筛选回收(Cleanup,STW):筛选回收阶段首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿STW时间(可以用JVM参数 -XX:MaxGCPauseMillis指定)来制定回收计划,比如说老年代此时有1000个Region都满了,但是因为根据预期停顿时间,本次垃圾回收可能只能停顿200毫秒,那么通过之前回收成本计算得知,可能回收其中800个Region刚好需要200ms,那么就只会回收800个Region(Collection Set,要回收的集合),尽量把GC导致的停顿时间控制在我们指定的范围内。这个阶段其实也可以做到与用户程序一起并发执行,但是因为只回收一部分Region,时间是用户可控制的,而且停顿用户线程将大幅提高收集效率。不管是年轻代或是老年代,回收算法主要用的是复制算法,将一个region中的存活对象复制到另一个region中,这种不会像CMS那样回收完因为有很多内存碎片还需要整理一次,G1采用复制算法回收几乎不会有太多内存碎片。(注意:CMS回收阶段是跟用户线程一起并发执行的,G1因为内部实现太复杂暂时没实现并发回收,不过到了ZGC,Shenandoah就实现了并发收集,Shenandoah可以看成是G1的升级版本)

在这里插入图片描述

G1垃圾收集分类

YoungGC
       YoungGC并不是说现有的Eden区放满了就会马上触发,G1会计算下现在Eden区回收大概要多久时间,如果回收时间远远小于参数 -XX:MaxGCPauseMills 设定的值,那么增加年轻代的region,继续给新对象存放,不会马上做Young GC,直到下一次Eden区放满,G1计算回收时间接近参数 -XX:MaxGCPauseMills 设定的值,那么就会触发Young GC
MixedGC
       不是FullGC,老年代的堆占有率达到参数(-XX:InitiatingHeapOccupancyPercent)设定的值则触发,回收所有的Young和部分Old(根据期望的GC停顿时间确定old区垃圾收集的优先顺序)以及大对象区(Humongous),正常情况G1的垃圾收集是先做MixedGC,主要使用复制算法,需要把各个region中存活的对象拷贝到别的region里去,拷贝过程中如果发现没有足够的空region能够承载拷贝对象就会触发一次Full GC
Full GC
       停止系统程序,然后采用单线程进行标记、清理和压缩整理,好空闲出来一批Region来供下一次MixedGC使用,这个过程是非常耗时的。(Shenandoah优化成多线程收集了)

G1特点:

  • 并行与并发:G1能充分利用CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短Stop-The-World停顿时间。部分其他收集器原本需要停顿Java线程来执行GC动作,G1收集器仍然可以通过并发的方式让java程序继续执行。
  • 分代收集:虽然G1可以不需要其他收集器配合就能独立管理整个GC堆,但是还是保留了分代的概念。
  • 空间整合:与CMS的“标记–清理”算法不同,G1从整体来看是基于“标记整理”算法实现的收集器;从局部上来看是基于“复制”算法实现的。
  • 可预测的停顿:这是G1相对于CMS的另一个大优势,降低停顿时间是G1 和 CMS 共同的关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段(通过参数"-XX:MaxGCPauseMillis"指定)内完成垃圾收集。

三色标记算法(读写屏障):

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

在这里插入图片描述

存在的问题:

  1. 如果在并发标记的过程中,有未被标记的对象(上图D)重新被标记对象(上图A)引用,此时怎么重新标记?
  2. 如果在并发标记的过程中,被标记的对象的引用被断开,怎样实现对该对象的垃圾回收?

解决方案:

  • 问题1:增量更新和原始快照SATB (写屏障)
    • 增量更新:D在被A引用,将这个引用记录下来,在重新标记时,将A变成灰色,重新对A的引用进行标记。CMS采用增量更新
    • 原始快照:D在被B删除引用,将D记录下来,在重新标记时,将D直接置为黑色,如果D没有被A再次引用,此时D作为浮动垃圾,再下一次GC的时候回收。G1采用 原始快照SATB
  • 问题2:作为浮动垃圾,再下一次GC的时候回收。

为什么G1用SATB?CMS用增量更新?

  • 我的理解:SATB相对增量更新效率会高(当然SATB可能造成更多的浮动垃圾),因为不需要在重新标记阶段再次深度扫描被删除引用对象,而CMS对增量引用的根对象会做深度扫描,G1因为很多对象都位于不同的region,CMS就一块老年代区域,重新深度扫描对象的话G1的代价会比CMS高,所以G1选择SATB不深度扫描对象,只是简单标记,等到下一轮GC再深度扫描。

类加载机制

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值