1、判断对象是否已经“死去”
垃圾回收器在对堆进行垃圾回收时,首先要做的就是判断对象是否已经死去。
1.1 引用计数法
原理:给对象添加一个引用计数器,当被引用时,引用计数器加一,当引用失效时,引用计数器减一,当引用计数器为零时,则表示这个对象已经死去。
优点:实现简单,判定效率高。
缺点:很难解决对象之间相互循环引用的问题。
1.2 可达性分析法
原理:以一系列的GC Roots为起点(可以作为GC Roots的对象包括:虚拟机栈中引用的对象、方法区类静态属性引用的对象、方法区常量引用的对象、本地方法栈的JNI
引用
的对象),沿着引用链往下找,能找到的对象则为活着的对象,若找不到,则证明该对象已经死亡。
1.3 两次标记
在进行可达性分析之后判定为死亡的对象此时还不是真正的死了,而是出于一种缓刑状态。要进行两次标记才能确定一个对象真正的死亡。第一次标记为可达性分析找不
到的对象。进行第一次标记后会对对象进行一次筛选,筛选的条件就是有没有必要执行finalize方法,如果该队形没有重写finalize方法或者finalize方法已经执行过则证明是
没有必要执行的。若该对象被判定为有必要执行finalize方法,则会被放进一个F-Queue中。finalize方法是对象逃脱死亡命运的最后机会,之后会对其进行第二次标记。若
对象在finalize方法中被重新引用,则逃脱死亡命运;若对象在finalize方法中仍没有逃脱死亡命运,则他就是真的死了。
2、垃圾收集算法
2.1 标记-清除算法
步骤:标记(如上所述)---清除。
缺点:产生大量的内存碎片,为以后为大对象分配内存带来困难。
用处:主要用于老生代。
2.2 标记-整理算法
这是对标记-清除算法的一种改进。
步骤:标记---将所有对象移动到内存的一端---清理掉端边界之外的内存
优点:解决了标记-清除算法产生大量内存碎片的问题。
用处:老生代。
2.3 复制算法
原理:将内存分配为一块较大的Eden区域和两块较小的Survivor区域(8:1:1),每次使用Eden和其中的一块Survivor。回收时将活着的对象复制到另外一块Survivor上,然后清除掉
Eden和刚才使用过的Survivor的内存空间。
优点:效率高,不用担心内存碎片的问题。
用处:主要用于新生代。
2.4 分代收集算法
老生代使用标记-清除或者标记-整理算法,新生代使用复制算法。
3、垃圾收集器
3.1 Serial
方法:使用的是复制算法,进行垃圾收集时暂停所有用户线程,知道他收集完毕。
缺点:进行垃圾收集时要暂停所有用户线程。
优点:与其他收集器的单线程比要更加简单、高效;对于单CPU环境,没有线程交互的开销。对于运行在客户端的虚拟机来讲仍是一个很好的选择。
3.2 ParNew
方法:使用的是复制算法,它其实就是Serial的多线程版本,不同之处只是使用多线程进行垃圾回收。
缺点:在单CPU环境下,由于存在线程交互,其性能是不如Serial的。
优点:只有ParNew和Serial能够与CMS配合使用。
3.3 Parallel Scavenge
和ParNew大致相同,只不过Parallel更加关注的是吞吐量,而ParNew更加关注的是停顿时间。
3.4 Serial Old
Serial的老生代版本,使用标记-整理算法。
3.5 Parallel Old
Parallel Scavenge的老生代算法,使用的是标记-整理算法。
3.6 CMS
概述:Concurrent Mark Sweep采用标记-清除算法,以获取最短回收停顿时间为目标。
步骤:初始标记(单线程)---并发标记---重新标记(多线程)---并发清除,其中初始标记和重新标记是要暂停所有用户线程的。
优点:由于最耗时的并发标记(可达性分析过程)和并发清除过程是最耗时的,而这两个过程是和用户线程并发进行的,故停顿时间短。
缺点:并发阶段会占用CPU资源而导致应用程序变慢,吞吐量降低;在并发清除阶段,程序还在运行,会产生新的垃圾,CMS无法再此次收集中收集这些新的垃圾;标记-
清除算法会产生内存碎片。
3.7 G1
特点:1. 分代收集;2.使用标记-整理算法;3.并发与并行;4.可预测的停顿。
步骤:初始标记(单线程)---并发标记---最终标记(多线程)---筛选回收(多线程)
可预测的停顿:G1将整个java堆分为多个大小相等的独立区域,在最后的筛选回收阶段首先对各个区域的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定
回收计划。