jvmGC(GarbageCollection)机制

一、GC 是什么? 为什么要有 GC?

  • Java 提供的 GC 功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的
  • Java 语言没有提供释放已分配内存的显示操作方法。

二、如何确定对象为垃圾可以回收

1. 引用计数法:
  • 在对象头处维护一个counter,每增加一次对该对象的引用计数器自加,如果对该对象的引用失联,则计数器自减。当counter为0时,表明该对象已经被废弃,不处于存活状态。这种方式一方面无法区分软、虛、弱、强引用类别。另一方面,会造成死锁,假设两个对象相互引用始终无法释放counter,永远不能GC。
  • 所谓引用计数法就是给每一个对象设置一个引用计数器,每当有一 个地方引用这个对象时,就将计数器加一,引用失效时,计数器就 减一。当一个对象的引用计数器为零时,说明此对象没有被引用, 也就是“死对象”,将会被垃圾回收.
  • 缺陷:无法解决循环引用问题,也就是说当对 象 A 引用对象 B,对象 B 又引用者对象 A,那么此时 A、B 对 象的引用计数器都不为零,也就造成无法完成垃圾回收,所以主流 的虚拟机都没有采用这种算法。
2. 可达性分析

在 Java 中可以作为 GC Roots 的对象有以下几种:

  • 虚拟机栈中引用的对象

  • 元数据区类静态属性引用的对象

  • 元数据区常量池引用的对象

  • 本地方法栈JNI引用的对象

    在jdk1.2之后,java对引用概念进行扩充,将引用分为强引用,软引用,弱引用,虚引用四种

  • 强引用:程序代码之中普遍存在的,object obj = new Object()这类引用,只要强引用存在,垃圾收集器就永远不会回收掉被引用对象。

  • 软引用是用来描述有用但是非必须的对象,对于软引用所关联的对象,在系统将要发生内存的溢出异常之前,会把这些对象列入回收范围之中进行第二次回收。jdk1.2提供了softReference类实现软引用。

  • 弱引用也是用来描述非必须的对象的,但是他的强度比软引用更弱一些,弱引用关联的对象只能生存到下一次垃圾收集器发生之前,jdk1.2提供weakReference类实现

  • 虚引用是最弱的一种引用关系,一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法用过虚引用获取对象实例,使用虚引用目的就是能再这个对象被收集器回收时收到一个系统通知。

思想:从一个被称为 GC Roots 的对象开始向下搜索, 如果一个对象到 GC Roots 没有任何引用链相连时,则说明此对 象不可用。

过程:如果对象在可达性分析中没有与 GC Root 的引用链,那么此时就会被第一次标记并且进行一次筛选,筛选的条件是是否有必要执行 finalize() 方法。当对象没有覆盖 finalize() 方法或者已被虚拟机调用过,那么就认为是没必要的。 如果该对象有必要执行 finalize() 方法,那么这个对象将会放在一个称为 F-Queue 的对 队列中,虚拟机会触发一个 Finalize() 线程去执行,此线程是低优先级的,并且虚拟机不会承诺一直等待它运行完。(这是因为如果 finalize() 执行缓慢或者发生了死锁,那么就会造成 F-Queue 队列一直等待,造成了内存回收系统的崩溃。)GC 对处于 F-Queue 中的对象进行第二次被标记,这时,该对象将被移除” 即将回收” 集合,等待回收。

3.枚举根节点

-枚举根节点也就是查找GC Roots;
- 目前主流JVM都是准确式GC,可以直接得知哪些地方存放着对象引用,所以执行系统停顿下来后,并不需要全部、逐个检查完全局性的和执行上下文中的引用位置;

  - 在HotSpot中,是使用一组称为OopMap的数据结构来达到这个目的的;
  - 在类加载时,计算对象内什么偏移量上是什么类型的数据;
 -  在JIT编译时,也会记录栈和寄存器中的哪些位置是引用;
  - 这样GC扫描时就可以直接得知这些信息;
4.安全点(safepoint)

1. 安全点是什么,为什么需要安全点
HotSpot在OopMap的帮助下可以快速且准确的完成GC Roots枚举,但是这有一个问题:
运行中,非常多的指令都会导致引用关系变化;
如果为这些指令都生成对应的OopMap,需要的空间成本太高;
问题解决:
只在特定的位置记录OopMap引用关系,这些位置称为安全点(Safepoint);
即程序执行时并非所有地方都能停顿下来开始GC;
2. 安全点的选定
不能太少,否则GC等待时间太长;也不能太多,否则GC过于频繁,增大运行时负荷;
所以,基本上是以程序"是否具有让程序长时间执行的特征"为标准选定;
"长时间执行"最明显的特征就是指令序列复用,如:方法调用、循环跳转、循环的末尾、异常跳转等;
只有具有这些功能的指令才会产生Safepoint;
3. 如何在安全点上停顿
对于Safepoint,如何在GC发生时让所有线程(不包括JNI线程)运行到其所在最近的Safepoint上再停顿下来?
主要有两种方案可选:
(A)、抢先式中断(Preemptive Suspension)
不需要线程主动配合,实现如下:
在GC发生时,首先中断所有线程;
如果发现不在Safepoint上的线程,就恢复让其运行到Safepoint上;
现在几乎没有JVM实现采用这种方式;
(B)、主动式中断(Voluntary Suspension)
在GC发生时,不直接操作线程中断,而是仅简单设置一个标志;
让各线程执行时主动去轮询这个标志,发现中断标志为真时就自己中断挂起;
而轮询标志的地方和Safepoint是重合的;
在JIT执行方式下:test指令是HotSpot生成的轮询指令;
一条test汇编指令便完成Safepoint轮询和触发线程中断;

5.安全区域
  • 1、为什么需要安全区域
    • 对于上面的Safepoint还有一个问题:
      程序不执行时没有CPU时间(Sleep或Blocked状态),无法运行到Safepoint上再中断挂起
      这就需要安全区域来解决;
  • 2、什么是安全区域(Safe Region)
    • 指一段代码片段中,引用关系不会发生变化;
      在这个区域中的任意地方开始GC都是安全的;
  • 3、如何用安全区域解决问题
    安全区域解决问题的思路:
    (1)、线程执行进入Safe Region,首先标识自己已经进入Safe Region;
    (2)、线程被唤醒离开Safe Region时,其需要检查系统是否已经完成根节点枚举(或整个GC);
    如果已经完成,就继续执行;
    否则必须等待,直到收到可以安全离开Safe Region的信号通知;
    这样就不会影响标记结果;

三、垃圾如何回收(回收算法)

标记清除算法(Mark-Sweep)
  • 最基础的垃圾回收算法,分为两个阶段,标注和清除。标记阶段标记出所有需要回收的对象,清 除阶段回收被标记的对象所占用的空间
    该算法大的问题是内存碎片化严重,后续可能发生大对象不能找到可 利用空间的问题
  • 该算法大的问题是内存碎片化严重,后续可能发生大对象不能找到可利用空间的问题
复制算法(Copying)
  • 按内存容量将内存划分为等大小 的两块。每次只使用其中一块,当这一块内存满后将尚存活的对象复制到另一块上去,把已使用 的内存清掉,如图:
    在这里插入图片描述
  • 优点:内存效率不高,不易产生碎片。
  • 缺点:可用内存缩小到原来的一半,如果存活对象增多,算法效率会大大降低
标记整理算法(Mark-Compact)
  • 标记阶段和Mark-Sweep算法相同,标记后不是清 理对象,而是将存活对象移向内存的一端。然后清除端边界外的对象
    在这里插入图片描述

四、分代执行回收算法

新生代
  • 目前大部分JVM的GC 对于新生代都采取Copying算法,因为新生代中每次垃圾回收都要 回收大部分对象,即要复制的操作比较少。
  • 但通常按照8:1:1来划分新生代。一般将新生代划分为一块较大的Eden空间和两个较小的Survivor空间(From Space, To Space)
  • 当进行回收时,将该两块空间(eden和from space)中还存活的对象复制到另 一块To Space空间中。 如果To Space无法足够存储某个对象,则将这个对象存储到老生代。
  • 当对象在Survivor区躲过一次GC 后,其年龄就会+1。默认情况下年龄到达15 的对象会被 移到老生代中。
    在这里插入图片描述
老年代
  • 而老年代因为每次只回收少量对象,因而采用Mark-Compact算法。

五、各种GC垃圾收集器

在这里插入图片描述

1. Serial垃圾收集器
  • Serial(英文连续)是基本垃圾收集器,使用复制算法
  • 是java虚拟机运行在Client模式下默认的新生代垃圾收集器。
2. ParNew垃圾收集器
  • ParNew垃圾收集器其实是Serial收集器的多线程版本,也使用复制算法
  • 很多java 虚拟机运行在Server模式下新生代的默认垃圾收集器。
  • Parallel Scavenger收集器
    • 同样使用复制算法,也是一个多线程的垃圾收集器
    • 自适应调节策略也是 ParallelScavenge 收集器与 ParNew 收集器的一个 重要区别
3.serial Old收集器
  • 它同样是个单线程的收集器,使用标记-整理算法
  • Parallel Old收集器
    • 使用多线程的标记-整理算法,在JDK1.6 才开始提供。
    • 在年老代同样提供吞 吐量优先的垃圾收集器
4. Concurrent mark sweep(CMS)收集器
  • 它使用多线程的标记-清除算法。 是一种以最短回收停顿时间为目标的收集器,常用在B/S系统
    • 初始标记
    • 并发标记
    • 重新标记
    • 并发清除
    • 其中,初始标记和重新标记仍需要“stop the world”初始标记就是标记一下GC root能直接关联到的对象,速度很快,并发标记阶段就是进行GC root Tracing的过程,而重新标记阶段则是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段停顿会比初始标记阶段稍长,但远比并发标记时间短
      在这里插入图片描述
  • CMS收集器的缺点
    • 对cpu资源非常敏感,在并发阶段,虽然不会导致用户线程停顿,但是占用了一部分的cpu资源会导致应用程序变慢,总吞吐量降低。
    • 无法处理浮动垃圾(伴随程序运行产生的新垃圾,无法再当次收集中处理),因此CMS收集器不会等到老年代被完全填满再进行收集。如果预留的内存无法满足程序需要就会触发虚拟机的后备预案,使用Serial Old收集器,这样停顿时间就很长了。
    • CMS是基于标记清除算法,收集结束时会有大量空间碎片产生。当老年代虽然有空间,但是无法找到足够大的连续空间是,就会触发full GC
  • G1收集器
    • 并行与并发:G1 能充分利用多 CPU、多核环境下的硬件优势,使用多个 CPU(CPU 或者 CPU 核心)来缩短 Stop-The-World 停顿的时间,部分其他收集器原本需要停顿 Java 线程执行的 GC 动作,G1 收集器仍然可以通过并发的方式让 Java 程序继续执行。
    • 分代收集:与其他收集器一样,分代概念在 G1 中依然得以保留。虽然 G1 可以不需其他收集器配合就能独立管理整个 GC 堆,但它能够采用不同的方式去处理新创建的对象和已经存活了一段时间、熬过多次 GC 的旧对象以获取更好的收集效果
    • 与 CMS 的“标记 - 清理”算法不同,G1 从整体看来是基于“标记 - 整理”算法实现的收集器,从局部(两个 Region 之间)上看是基于“复制”算法实现,无论如何,这两种算法都意味着 G1 运作期间不会产生内存空间碎片,收集后能提供规整的可用内存。这种特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前触发下一次 GC。
    • 可预测的停顿:这是 G1 相对于 CMS 的另外一大优势,降低停顿时间是 G1 和 CMS 共同的关注点,但 G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M 毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N 毫秒,这几乎已经是实时Java(RTSJ)的垃圾收集器特征了。
  • 对 CMS 收集器运作过程熟悉的读者,一定已经发现 G1 的前几个步骤的运作过程和 CMS 有很多相似之处。初始标记阶段仅仅只是标记一下 GC Roots 能直接关联到的对象,并且修改 TAMS(Next Top at Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可用的 Region 中创建新对象,这阶段需要停顿线程,但耗时很短。并发标记阶段是从 GC Root 开始对堆中对象进行可达性分析,找出存活的对象,这阶段耗时较长,但可与用户程序并发执行。而最终标记阶段则是为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程 Remembered Set Logs 里面,最终标记阶段需要把 Remembered Set Logs 的数据合并到 Remembered Set 中,这阶段需要停顿线程,但是可并行执行。最后筛选回收阶段首先对各个 Region 的回收价值和成本进行排序,根据用户所期望的 GC 停顿时间来制定回收计划,从 Sun 透露出来的信息来看,这个阶段其实也可以做到与用户程序一起并发执行,但是因为只回收一部分 Region,时间是用户可控制的,而且停顿用户线程将大幅提高收集效率。
    在这里插入图片描述

    六、永久代(元数据区)的垃圾回收

    • 垃圾回收不会发生在永久代,如果永久代满了或者是超过了临界值,才会触发垃圾回收(full gc)

参考

  1. https://www.infoq.cn/article/jvm-memory-collection/
  2. 《深入理解java虚拟机》-周志明
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值