jvm-垃圾回收器

垃圾回收的相关概念

System.gc()

默认情况下,通过system gc 的调用会显示触发Full GC,同时对新生代和老年代进行回收,尝试释放被丢弃对象占用的内存。

内存溢出

javadoc 对OOM的解释是没有空闲内存,并且垃圾收集器也无法提供更多的内存

  • 没有空闲内存说明java虚拟机的堆内存不够,比如可能存在内存泄漏问题,也有可能堆的大小不合理;代码中创建了大量的大对象,并且长时间不能被垃圾收集器收集;老版本JVM对永久代垃圾回收非常不积极,元空间的引入,出现OOM :metaspace
  • 在抛出OOM异常之前,通常垃圾收集器会被触发尽其所能去收集空间 ,尝试回收软引用指向的对象,system gc 调用; 当然也不是任何情况垃圾回收器都会被触发,分配一个超级大的对象,JVM判断垃圾收集器不能解决的这个问题,会直接抛出OOM

内存泄漏

严格来说,只有对象不会被程序用到但是GC又不能回收他们的情况,才叫内存泄漏。但是实际情况一些不好的实践也会导致对象的生命周期变得很长,甚至OOM,也可以叫做宽泛意义上的内存泄漏。这里的内存不是指物理内存,而是虚拟机内存大小

STW

stop the world 简称STW ,指的是GC事件发生过程中,会产生应用程序停顿,停顿产生的时候整个应用程序线程都会被暂停,没有任何响应,STW事件和采用哪款GC无关,STW是JVM后台自动发起和完成的,在用户不可见的情况下,把用户正常的工作线程全部停掉

安全点

程序执行时并非在所有地方都能停下来开始GC,只有在特定位置才能停下来开始GC,这些位置称为安全点,安全点的选择十分重要,如果太少可能导致GC等待时间太长,太频繁就会导致运行时的性能问题,通常会根据是否具有让程序长时间执行的特性为标准,比如选一些执行指令(方法调用,循环跳转,异常跳转等)为安全点,在GC发生时,设置一个中断标志,各个线程运行到安全点的时候主动轮询这个标志,为true就将自己进行的中断挂起

在垃圾回收器的眼中只有垃圾回收线程(collector线程)以及修改对象的线程(Mutator线程),由于垃圾回收线程也需要修改对象,尤其在垃圾回收的过程中可能有移动对象情况,在垃圾回收时一般需要全过程或者部分过程暂停mutator线程,这种现象叫做STW,在hotspot 使用安全点作为制度性STw机制,安全点本质是一页内存。虚拟机将 读取安全点内存页 的操作安插在合适的地方,当程序没有请求垃圾回收时,安全点内存页可读(good page),mutator线程对安全点的访问怒会引发问题,当需要垃圾回收,VMThread将安全点设置为不可读不可写(bad page),然后等待所有mutator线程走到安全点,由于mutator线程访问不可读不可写的内存会引发异常信号,虚拟机可以通过内部信号处理器捕获并停止mutator线程的执行,这样相当于让所有mutator线程主动停止。

安全区域

指一段代码片段,对象的引用关系不会发生变化,这个区域的任何位置开始GC都是安全的。

safepointSynchronize begin ,safepointSynchronize end 分别表示安全点的开启和关闭,两者之间构成了一个安全区域。VMThread会等待所有线程,直到都达到安全点,此时安全点开启成功,开启安全点的核心是线程状态的转换,不同线程进入安全点的方式也不同。

垃圾回收器概述

垃圾回收器可以由不同厂商、不同版本的JVM实现,从不同的角度可以将GC分为不同

  1. 吞吐量 :CPU运行用户代码的时间与CPU总消耗时间的比值
  2. 暂停时间:执行垃圾收集时,程序的工作线程被暂停的时间
  3. 内存占用:java堆区所占用的内存大小

Serial 回收器:串行回收

Serial回收器是最基本历史最悠久的垃圾收集器,serial(serial old)作为hotspot中client模式下的默认新生代(老年代)垃圾收集器,是单线程收集器,在进行垃圾收集时必须暂停其他所有的工作线程,直到收集结束。新生代采用复制算法,老年代采用标记整理算法。

Parallel 回收器:吞吐量优先

新生代采用复制算法,老年代采用标记整理算法。

CMS 回收器:低延迟(并发收集)

第一款真正意义上的并发收集器,第一次实现了让垃圾收集线程和用户线程同时工作。整个过程分为4个主要阶段

  • 初始标记:程序中所有工作线程都会因为STW机制出现短暂的暂停,这个阶段主要任务是标记出GC roots能直接关联的对象,一旦标记完后就会互动之前被暂停的所有应用线程。
  • 并发标记:从GC root的直接关联对象开始遍历整个对象图的过程,这个过程耗时但是不需要停顿用户线程,可以与垃圾收集线程要一起并发运行。
  • 重新标记:为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间通常会比初始阶段稍微长一些,并且会导致STW,但也远比并发标记阶段短
  • 并发清除:清理删除掉标记阶段判断的已经死亡的对象,释放内存空间,由于不需要移动存活对象,所以这个阶段可以与用户线程同时并发

注意在垃圾收集阶段用户线程并没有中断,所以在CMS回收过程中,应该确保用户线程有足够的内存可用,当堆内存使用率达到某一阈值便开始回收,CMS垃圾收集算法采用的标记清除算法,不可避免的产生一些内存碎片,在为新对象分配内存空间时候只能选择空闲列表执行内存分配。

G1 回收器:区域化分代式

G1 包含YGC,FGC, Mixed GC ,YGC只会回收新生代Region,而FGC会回收整个堆。独有的Mixed GC会回收新生代Region 以及部分老年代Region,会面临跨代引用问题,一个老年代Region的根集包括GC root 以及从老年代指向老年代的引用,新生代的根集包括GC root以及老年代Region指向新生代Region的引用。

回收过程1:年轻代GC

  1. 扫描根:跟引用连同Rset记录的外部引用作为扫描存活对象的入口。

在进行垃圾回收之前会创建Cset(Collection Set)存放需要被清理的Region, 选择合适的Region是放入Cset是为了让G1达到用户期望的合理的停顿时间。
这一阶段会将根集直接可达的对象复制到s Region,并将这些对象的成员放入队列,然后更新根集指向

  1. 更新rset:
  2. 扫描rset:识别被老年代指向的Eden区中的对象,这些被指向的Eden中的对象被认为是存活的对象

第一阶段标记了从GC root 到eden Region的对象,对于从old Region 到eden Region的对象则需要借助Rset,这一步包括更新Rset以及扫描 RSet 遍历Cset中所有的Region ,找到引用者并将其作为起点开始标记存活对象。

  1. 复制对象: 对象树被遍历,Eden区内存段中存活的对象会被复制到s区中空的内存分段,s区内存段中存活的对象如果年龄未达阈值会被加一,达到阈值会被赋值到old区中空的内存分段,如果s空间不够,Eden空间的部分数据会直接晋升到老年代空间。

经过前面的步骤,YGC发现所有的存活对象都会位于G1ParScanThreadState 队列,对象复制则是将队列中的所有存活对象复制到s Region或者晋升到Old Region。对象复制是YGC的最后一步,在这之后新生代所有存活对象都会被移动到S区或者晋升到Old Region,之前的Eden 空间可以被回收。

  1. 处理引用:处理soft weak 等引用,最终eden空间的数据为空,GC停止工作,而目标内存中的对象都是连续存储的,没有碎片,所以复制过程可以达到内存整理的效果,减少碎片。

回收过程2:并发标记过程

  1. 初始标记阶段:标记从根节点直接可达的对象,这个阶段STW 并且会触发一次YongGC,只扫描根节点可达的对象。

初始标记是全局并发标记的第一阶段,是STW的,这一阶段会扫描GC root直接可达的对象,并把他们复制到S Region,该过程与YGC一致,所以复用了YGC的代码,

  1. 根区域扫描:G1 GC扫描s区直接可达的老年代区域对象,并标记别引用的对象,这个过程必须在Yong GC之前完成,因为Yong GC回使用复制算法对s区进行GC

  2. 并发标记:在整个堆中进行并发标记,此过程可能被Yong GC中断,在并发标记阶段,若发现区域对象中所有对象都是垃圾,那这个区域会被立即回收。同时,并发标记过程,会计算每个区域的对象活性。

  3. 再次标记: 由于应用程序持续进行,需要修正上一次的标记结果。是STW ,G1中采用了更快的原始快照算法。

  4. 独占清理:计算几个区域的存活对象和GC回收比例,并进行排序识别可以混合回收的区域,是STW,这个阶段并不会实际上去做垃圾的收集。

  5. 并发清理阶段:识别并清理完全空闲的区域。

回收过程3:混合回收

  1. 当越来越多的对象晋升到老年代(old region),为了避免对堆内存被耗尽,虚拟机会触发一个混合的垃圾收集器(Mixed GC),除了回收整个Yong Regin,还会回收一部分的old region。
  2. 并发标记结束后,老年代百分百为垃圾的内存分段被回收了,部分为垃圾的内存分段被计算了出来。

回收过程4:Full GC

posted @ 2022-04-06 18:09  Henry19  阅读(86)  评论(0编辑  收藏  举报