JVM垃圾回收

什么需要回收

如何判断对象是否已死?

  • 引用计数法
    记录被引用的次数,当为0时就可以回收,确定会有循环依赖的问题,但是高效
  • 可达性分析法
    通过GCRoot为根节点开始查找,能找到的为存活对象,否则为需要清理的对象
    GCRoot包括,虚拟机栈(局部变量表中引用的对象),方法区中类静态属性引用对象,方法区中常量引用对象,本地方法栈中引用对象(Native引用对象)

其他说明

  • 方法区的回收判断条件比较苛刻
    该类所有对象都已经被回收
    类的加载器已经被回收
    类对应的Class对象没有任何地方被访问,没有被反射访问
  • 枚举根节点
    可达性分享中寻找GCRoot会特别难,比如只是扫描方法区可能就数百兆。所以需要记录枚举根节点。在HotSpot的实现使用OopMap数据结构来存储,每次类加载完成后放入OopMap中GC的时候就可以直接使用
  • 安全区
    为了解决每一条指令都放入OopMap中可能导致空间太大,所以引入安全区的概念,当GC发生时所有线程都执行到安全区然后标示自己已经进入safeRegion,当出去时要判断是否已经完成根节点的枚举(或整个GC过程)

垃圾回收算法

标记清除法

  • 分为标记和清除两步,先标记需要回收对象,然后进行清除
  • 问题:效率慢,会产生大量不连续的内存碎片

复制算法

  • 将空间划分使用和未使用的,每次清理的时候将存活对象存入未使用区域,然后直接清除已使用的区域
  • 问题:存活率高时,会造成效率变低。会造成内存的浪费。(新生代使用的回收算法)

标记整理法

  • 将存活的对象向一端移动,最后清除掉其他区域
  • 问题:效率慢

分代收集算法

  • 将内存划分代,根据各个代的特点使用不同的回收算法,如:新生代使用复制算法,老年代使用标记清除

垃圾回收器

连线代表可以一块使用

作用于新生代的垃圾回收器有:Serial,ParNew,Parallel Scavenge
作用于老年代的垃圾回收器有:CMS,Serial Old, Parallel Old
G1收集器即可以作用于新生代又可以作用域老年代

  • Serial收集器
    所有线程跑到安全区后暂停,开启单线程进行清理。清理完成后线程开始执行,采用复制算法
  • ParNew收集器
    Serial收集器多线程版本,清理是开启多线程
  • Parallel Scavenge收集器
    Parallel Scavenge收集器也是新生代收,复制算法、多线程收集器。于ParNew不同的是关注的是一个可控制的吞吐量,可以设置最大垃圾收集停顿时间以及直接设置吞吐量大小
  • Serial Old收集器
    老年代的收集器,采用标记整理的算法。主要在Client模式下的虚拟机使用
  • Parallel Old收集器
    是Parallel Scavenge老年代版本,采用标记整理的算法,只能和新生代的Parallel Scavenge收集器共同使用
  • CMS收集器


    image.png

CMS是以获取最短停顿时间为目标的收集器,采用标记-清除的算法,分为4步:初始标记-》并发标记-》重新标记-》并发清除
初始标记:标记GCRoot。很快,但需要STW
并发标记:通过初始标记的GCRoot去追踪标记内存中的对象,和用户线程一块跑(耗时较长)
重新标记:修正在在并发标记有变动的部分,停顿时间比初始标记长但远比并发标记短,需要STW
并发清除:和用户线程一块进行清除垃圾数据
问题:会有碎片,对CPU要求高只有一个CPU的话性能反而会低,产生浮动垃圾(就是清理的时候又产生了垃圾)

  • G1收集器


    image.png

G1一个Region的大小可以通过参数-XX:G1HeapRegionSize设定取值范围从1M到32M,且是2的指数。如果不设定,那么G1会根据Heap大小自动决定,内存大小/2048

初始标记(initial mark,STW)

初始标记只是标记一下 GC Roots 能直接关联的对象,速度很快,仍然需要暂停所有的工作线程(STW)

与CMS相同标记使用三色标记

  • 白:对象没有被标记到,标记阶段结束后,会被当做垃圾回收掉。
  • 灰:对象被标记了,但是它的field还没有被标记或标记完。
  • 黑:对象被标记了,且它的所有field也被标记完了。

并发标记(Concurrent Marking),不需要STW

从GC Roots开始对堆的对象进行可达性分析,递归扫描整个堆里的对象图,找出存活的对象,这阶段耗时较长,但是可以与用户程序并发执行。
当对象图扫描完成以后,还要重新处理SATB记录下的在并发时有引用变动的对象。

最终标记(Remark,STW)

对用户线程做另一个短暂的暂停,用于处理并发阶段结束后仍遗留下来的最后那少量的 SATB 记录

筛选回收(Cleanup,STW)

这个过程是和CMS不同的,CMS并发清理是和用户线程并发执行的。而G1的筛选回收是Stop The World的,对各个 Region 的回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划(可预测停顿),G1从整体来看是基于“标记整理”算法实现的收集器;从局部 上来看是基于“复制”算法实现的

为什么需要最终标记,为什么最终标记需要STW?

在并发标记时由于用户线程还在跑所以又可能,将对象的引用修改,比如
A->B,C->D,如果标记线程把A和B都标记完了(A和B都成了黑色节点),此时用户线程B指向了D,这个时候,D就会是白色,在清理的时候就会被回收,但D时存活对象

这种情况发生的前提是:

  • Mutator赋予一个黑对象该白对象的引用。
  • Mutator删除了所有从灰对象到该白对象的直接或者间接引用

对于CMS的的处理是在修改引用时将修改的对象标记为灰色,这样就在重新标记的时候就可以标记到不会错误的清理,对于G1来说是通过记录修改的引用,在重新标记时再处理

CMS是基于增量更新来做并发标记的,G1则采用的是原始快照的方式

增量更新用的是写后屏障(Post-Write Barrier),记录了所有新增的引用关系,原始快照用的是写前屏障(Pre-Write Barrier),将所有即将被删除的引用关系的旧引用记录下来

垃圾回收时,用户线程新增对象和修改对象时如何保证回收正常

新增对象
image.png
  • 每个region有两个指针,bottom和top,代表region已经使用的空间
  • 初始标记时每个region中会有两个指针prevTAMS和nextTAMS,指向region此次垃圾回收时region使用空间(prevTAMS=bottom,nextTAMS=top)
  • 新增对象时,只会在nextTAMS之后的空间分配,此时top的指针会后移,nextTAMS不会动
  • 垃圾回收时只标记prevTAMS到topTAMS的空间的数据,会有一个bitMap去记录哪些空间在使用
image.png
修改对象引用

就是原始快照(STAB)加写前屏障(Pre-Wirte Barrier)工作的部分。
当 GC 线程扫描完对象图后,还需要重新处理 STAB 记录下的在并发时有引用变动的对象

为什么CMS停顿时间长?

CMS采用标记清理的垃圾回收策略,会产生大量的内存碎片,在分配大对象时,内存充足但是都是碎片的时候会触发内存整理导致STW时间较长,而G1通过region的复制不产生碎片,同时预测碎片的回收时间来达到可预测的STW

在标记时用户线程创建的新对象怎么处理?

在标记时,创建新对象后,由于GCRoot没有在扫描的集合里,所以节点是白色的,垃圾收集器应该如何处理?

什么情况下应该考虑使用G1

  • 实时数据占用超过一半的堆空间
  • 对象分配或者晋升的速度变化大
  • 希望消除长时间的GC停顿(超过0.5-1秒)

G1垃圾收集分为三种:一种是YoungGC,一种是MixedGC,另一种是Full GC

YoungGC

YoungGC就是MinorGC,原来的垃圾收集器都是Eden区放满了就出MinorGC,但是G1有所不同。之前说过,新生代占整个内存的5%,Eden区和Survivor的比例是8:1:1,不到5%。假如这些空间全部都放满了,会怎么样呢?是不是就立刻触发MinorGC了呢?不是的。那么何时触发minorGC呢?触发MinorGC的时间和-XX:MaxGCPauseMillis参数的值有关系。假如-XX:MaxGCPauseMillis=200ms,G1会计算回收这5%的空间耗时是不是接近200ms,如果是,那么就会触发MinorGC。如果不是,假如只有50ms,远远低于200ms,那么就不会触发MinorGC,他会把新的对象放到新的没有被占用的Region区中。直到Eden园区回收的时间接近200ms了,这时才会触发MinorGC。这就是刚开始新生代设置的空间是5%,但是在实际运行的过程中,很可能会超过5%,最大不能超过60%,60%是默认值,这个值可以通过“-XX:G1MaxNewSizePercent”参数调整。


image.png

YoungGC并不是说现有的Eden区放满了就会马上触发,G1会计算下现在Eden区回收大概要多久时间,如果回收时间远远小于参数 -XX:MaxGCPauseMills 设定的值,那么增加年轻代的region,继续给新对象存放,不会马上做Young GC,直到下一次Eden区放满,G1计算回收时间接近参数 -XX:MaxGCPauseMills 设定的值,那么就会触发Young GC 。

MixedGC

MixedGC和之前的FullGC优点相似,但他不是Full GC。G1有专门的Full GC。当老年代的堆占有率达到参数(-XX:InitiatingHeapOccupancyPercent)设定的值时则触发Mixed GC。回收时会回收所有的 Young区和部分Old区以及大对象区。为什么是一部分的Old区呢?它会根据GC的最大停顿时间来计算最高效益比,来确定old区垃圾收集的先后顺序。

正常情况G1的垃圾收集是先做 MixedGC,主要使用复制算法,需要把各个region中存活的对象拷贝到别的region里去,拷贝过程中如果发现没有足够 的空region能够承载拷贝对象就会触发一次Full GC.

Full GC

Mixed GC不是full GC,它只能回收部分老年代的Region,如果mixed GC实在无法跟上程序分配内存的速度,导致老年代填满无法继续进行Mixed GC,就会使用serial old GC(full GC)来收集整个GC heap。所以我们可以知道,G1是不提供full GC的

什么时候会触发Full GC呢?

在老年代,需要把region中存活的对象拷贝到别的region中去的时候,拷贝过程中发现没有足够的空region能够承载的拷贝对象了,就会触发Full GC。举个例子:假如MixedGC触发的条件-XX:InitiatingHeapOccupancyPercent=45%,而剩余50%的空间被新生代占了。那么还剩5%的空间。当-XX:InitiatingHeapOccupancyPercent的值达到了45%,触发MixedGC的时候,这个时候需要复制老年代对象到新的未被占用的Region区,很显然这时没有足够的Region区,这时会触发Full GC。

你可能感兴趣的:(JVM垃圾回收)