什么需要回收
如何判断对象是否已死?
- 引用计数法
记录被引用的次数,当为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收集器
CMS是以获取最短停顿时间为目标的收集器,采用标记-清除的算法,分为4步:初始标记-》并发标记-》重新标记-》并发清除
初始标记:标记GCRoot。很快,但需要STW
并发标记:通过初始标记的GCRoot去追踪标记内存中的对象,和用户线程一块跑(耗时较长)
重新标记:修正在在并发标记有变动的部分,停顿时间比初始标记长但远比并发标记短,需要STW
并发清除:和用户线程一块进行清除垃圾数据
问题:会有碎片,对CPU要求高只有一个CPU的话性能反而会低,产生浮动垃圾(就是清理的时候又产生了垃圾)
-
G1收集器
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),将所有即将被删除的引用关系的旧引用记录下来
垃圾回收时,用户线程新增对象和修改对象时如何保证回收正常
新增对象
- 每个region有两个指针,bottom和top,代表region已经使用的空间
- 初始标记时每个region中会有两个指针prevTAMS和nextTAMS,指向region此次垃圾回收时region使用空间(prevTAMS=bottom,nextTAMS=top)
- 新增对象时,只会在nextTAMS之后的空间分配,此时top的指针会后移,nextTAMS不会动
- 垃圾回收时只标记prevTAMS到topTAMS的空间的数据,会有一个bitMap去记录哪些空间在使用
修改对象引用
就是原始快照(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”参数调整。
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。