1、判断对象是否存活
(1)引用计数法:给对象添加一个引用计数器,每当有一个地方引用它时,计数器值就加1。无法解决循环引用的问题,无法表示强、软、弱、虚引用四种引用类型;
(2)可达性分析:以GC Roots为起点,向下搜索,搜索的路径称为引用链,当一个对象到GC Roots没有任何引用链相连,可回收。GC Roots包括【1】虚拟机栈(栈帧中的本地变量表)中引用的对象【2】本地方法栈中JNI(即Native方法)引用的对象【3】方法区中类静态属性引用的对象【4】方法区中常量引用的对象;
2、四种引用类型
(1)强引用:new出来的对象,只要强引用还存在,垃圾收集器永远不会回收被引用的对象;
(2)软引用:用来描述一些还有用但并非必需的对象,内存充足则不回收,在系统将要OOM之前回收所有软引用,常用于缓存,SoftReference实现软引用;
(3)弱引用:只能生存到下一次GC之前,无论内存是否足够,都会回收掉只被弱引用关联的对象,WeakReference;
(4)虚引用:唯一目的是给对象设置虚引用,在这个对象被回收时收到一个系统通知,跟踪GC,PhantomReference;
3、finalize()方法
在可达性分析中不可达的对象,到对象的死亡,还要经历两次标记过程。如果没有到GC Roots的引用链,将会被第一次标记并且进行筛选,筛选的条件是该对象是否有必要执行finalize()方法,当对象没有覆盖finalize(),或者已经执行过一次finalize()(任何一个对象的finalize()方法都只会被系统自动调用一次),则没有必要执行。
如果要执行finalize(),放入一个F-Queue队列,稍后进行第二次标记(finalize()不一定执行完,防止执行太慢阻塞队列),如果对象在finalize()中重新建立到GC Roots的引用链可以逃脱死亡,在第二次标记时被移除“即将回收”的集合,如果第二次标记还没有引用链就被回收。
4、垃圾回收算法
(1)标记—清除:标记的判定依据GC Roots和finalize()的执行结果。标记清除之后会产生大量不连续的内存碎片,常用于回收老年代;
(2)复制算法:回收新生代,新生代对象大都是朝生夕死的,Eden:From Survivor:To Survivor = 8:1:1;使用90%的空间,由老年代进行担保,如果To Survivor没有足够空间存放存活的对象,对象通过分配担保机制进入老年代;
(3)标记—整理:回收老年代;
(4)分代收集:将堆分为新生代和老年代,新生代用复制算法,老年代用标记—清除/整理算法;
5、OopMap、Safepoint、Safe Region
GC进行时一般要Stop The World,为了避免GC时对象引用关系不断变化。枚举GC Roots时必须Stop The World;
虚拟机应当有办法得知哪些地方存放着对象引用,HotSpot使用一组称为OopMap的数据结构,记录对象的类型和偏移量。OopMap可以快速完成GC Roots的枚举,但是不可能为每一条指令都生成OopMap,只能在特定的位置记录OopMap,称为安全点(Safepoint);
如果用Safepoint,需要等到所有线程都到达Safepoint再GC,可以将Safepoint扩展为Safe Region;
6、垃圾收集器,HotSpot虚拟机的垃圾收集器的种类如下,连线表示可以组合使用。并行(Parallel)表示有多条GC线程,仍然需要Stop the World;并发(Concurrent)表示用户线程和GC线程一起执行。
(1)Serial/Serial Old
Serial是最基本、最悠久的单线程收集器,需要Stop the World,简单高效,用复制算法收集新生代;Serial Old是Serial的老年代版本,用标记—整理算法收集老年代;
(2)ParNew,Serial的多线程版本,使用多条线程GC,需要Stop the World,用复制算法收集新生代;
(3)Parallel Scavenge/Parallel Old,一组多线程(并行)、吞吐量优先的垃圾收集器的组合。吞吐量 = 用户时间 / (用户时间 + GC时间),可以指定最大停顿时间和吞吐量的大小,虚拟机可以自适应的调整Eden:Survivor、晋升老年代对象大小等参数保证合适的停顿时间或最大吞吐量。
Parallel Scavenge用复制算法回收新生代,Parallel Old用标记—整理算法回收老年代;
(4)CMS(Concurrent Mark Sweep),第一款并发(Concurrent)的垃圾收集器,让GC线程与用户线程同时工作,以最短停顿时间为目标,用标记—清除算法回收老年代(因为是并发的,肯定不能用标记—整理了)。
分为四个步骤:【1】初始标记,需要Stop the World,仅仅标记GC Roots直接关联的对象,速度很快,时间很短【2】并发标记,和用户线程并行执行,进行GC Roots Tracing,时间长【3】重新标记,修正并发标记期间的改动,需要Stop the World,一般比初始标记时间略长,但远比并发标记的时间短【4】并发清除,耗时;四个过程中耗时最长的并发标记和并发清除过程都可以与用户线程一起,所以总体上是一款并发的垃圾收集器。
缺点:【1】CPU敏感,CMS默认启动的回收线程数是(CPU数量 + 3) / 4,对用户程序可能有较大影响【2】浮动垃圾,并发清除阶段会有新的垃圾产生,这使得并发清除时需要预留一定的内存空间,不能像其他收集器在老年代几乎填满时再进行收集,如果预留内存不足,会出现一次“Concurrent Mode Failure”,临时启用Serial Old收集器,并产生另一次Full GC【3】内存碎片;
(5)G1(Garbage-First),将堆划分为多个大小相等的独立区域(Region),新生代和老年代不再是物理隔离的了,从整体来看是基于标记—整理算法(移动Eden、Old等区域中存活的对象,避免碎片),从局部(两个Region之间)来看是基于复制算法(复制Eden或Old存活对象到Survivor)。
G1跟踪各个Region里垃圾堆积的价值大小,在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region,可以指定一定时间内GC的最大时间。在G1收集器中,Region之间的对象引用以及其他收集器中的新生代与老年代之间的对象引用,虚拟机都是使用Remembered Set来避免全堆扫描的。每个Region都有一个Remembered Set,维护不同Region之间的相互引用,便于做可达性分析。
步骤和CMS类似:【1】初始标记(Stop the World,单线程)【2】并发标记【3】最终标记(Stop the World,多线程)【4】筛选回收,Stop the World,多线程,只有这一步和CMS不同,需要停顿用户线程,回收垃圾的效率更高,避免产生浮动垃圾。因为用户可以设置停顿时间,所以是可以接受的。该阶段也可以并行,jdk1.9就改成了并行。
7、Minor/Full GC、何时Full GC、GC日志(不同的收集器日志格式有区别)
Minor GC:指发生在新生代的垃圾收集动作,非常频繁,回收速度快;
Major GC/Full GC:指发生在老年代的GC,经常伴随至少一次的Minor GC,Major GC的速度一般会比Minor GC慢10倍以上;
何时Full GC:老年代空间不足,剩余空间小于新生代的平均晋升大小;担保失败;永久代已满;System.gc();CMS GC时出现“Concurrent Mode Failure”等情况;
以下是两段典型的GC日志
33.125:[GC[DefNew:3324K->152K(3712K),0.0025925secs]3324K->152K(11904K),0.0031680 secs]
100.667:[FullGC[Tenured:0K->210K(10240K),0.0149142secs]4603K->210K(19456K),[Perm:2999K->2999K(21248K)],0.0150007 secs][Times:user=0.01 sys=0.00,real=0.02 secs]
最前面的数字“33.125”和“100.667”代表了GC发生的时间,是从虚拟机启动以来经过的秒数;
“GC”和“Full GC”说明了这次垃圾收集的停顿类型,而不是用来区分新生代GC还是老年代GC的。如果是“Full GC”表示发生了Stop the World,如果调用了System.gc(),显示Full GC(System);
“DefNew”、“Tenured”、“Perm”表示GC发生的区域,名字和使用的垃圾收集器有关,使用Serial(新生代名为Default New Generation)就是“DefNew”,使用ParNew就叫“ParNew”,使用Parallel Scavenge新生代叫“PSYoungGen”;
后面方括号内部的“3324K->152K(3712K)”表示“GC前该内存区域已使用容量->GC后该内存区域已使用容量(该内存区域总容量)”,方括号之外的“3324K->152K(11904K)”表示“GC前Java堆已使用容量->GC后Java堆已使用容量(Java堆总容量)”;
再往后,“0.0025925secs”表示该内存区域GC用时,有的收集器会给出更具体的时间,如“Times: user=0.01 sys=0.00, real=0.02 secs”,分别表示用户态、内核态消耗的CPU时间、墙钟时间(包括阻塞的时间);
8、内存分配与回收策略
(1)对象优先在Eden分配,由老年代担保;
(2)大对象(>3MB)直接进入老年代,避免复制算法的开销;
(3)长期存活的对象进入老年代,利用了对象头里的GC年龄,每熬过一次Minor GC,年龄+1,默认15岁晋升老年代;
(4)动态对象年龄判定,如果Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代;
(5)空间分配担保,Minor GC前虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总和,如果成立Minor GC安全;否则检查是否允许担保失败,允许则继续检查最大连续可用空间是否大于新生代历次晋升的平均大小,如果大于则尝试Minor GC,小于则Full GC;即只要老年代连续空间大于新生代对象总大小或历次晋升的平均大小就会Minor GC,否则Full GC;