1.垃圾对象判定算法
Java中根据不同的垃圾对象判定算法,判断哪些Java对象是不是垃圾。
1.1引用计数算法
给对象添加一个引用计数器,每当有一个地方引用它时,计数器值就加1,当引用失效时,计数器值就减1,任何时刻计数器都为0的对象就是不可能再被使用的。
优点:实现简单,效率高
缺点:很难解决对象之间的相互循环引用(所以一般jvm实现不用该算法)
1.2根搜索法(GC Roots Tracing)
Java主要是采用根搜索算法来判定对象是否存活的。这种算法的基本思路是通过一系列名为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连时,就证明此对象是不可用的。在Java语言里,可作为GC Roots的兑现包括下面几种:
- 虚拟机栈(栈帧中的本地变量表)中引用的对象。
- 方法区中的类静态属性引用的对象。
- 方法区中的常量引用的对象。
- 本地方法栈中JNI(Native方法)的引用对象。
实际上,在根搜索算法中,要真正宣告一个对象死亡,至少要经历两次标记过程:如果对象在进行根搜索后发现没有与GC Roots相连接的引用链,那它会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法,或finalize()方法已经被虚拟机调用过,虚拟机将这两种情况都视为没有必要执行。如果该对象被判定为有必要执行finalize()方法,那么这个对象将会被放置在一个名为F-Queue队列中,并在稍后由一条由虚拟机自动建立的、低优先级的Finalizer线程去执行finalize()方法。finalize()方法是对象逃脱死亡命运的最后一次机会(因为一个对象的finalize()方法最多只会被系统自动调用一次),稍后GC将对F-Queue中的对象进行第二次小规模的标记,如果要在finalize()方法中成功拯救自己,只要在finalize()方法中让该对象重引用链上的任何一个对象建立关联即可。而如果对象这时还没有关联到任何链上的引用,那它就会被回收掉。
2.垃圾收集算法
根据垃圾对象判断算法之后,得到了垃圾对象,就需要对这些垃圾进行回收了。看看常用的垃圾回收算法之间的区别:
2.1标记-清除算法 (Mark-Sweep)
标记-清除算法将垃圾回收分为两个阶段:标记阶段和清除阶段。一种可行的实现是,在标记阶段首先通过根节点,标记所有从根节点开始的较大对象。因此,未被标记的对象就是未被引用的垃圾对象。然后,在清除阶段,清除所有未被标记的对象。
优点:
- 实现简单
缺点: - 存在大量的空间碎片,无法分配大对象。
- 标记和清除过程的效率都不高(对象不存在连续分配的内存空间中)。
2.2复制算法 (Copying)
将现有的内存空间分为两快,每次只使用其中一块,在垃圾回收时将正在使用的内存中的存活对象复制到未被使用的内存块中,之后,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收。
优点:
- 每次只对一块内存进行回收,运行高效。
- 只需移动栈顶指针,按顺序分配内存即可,实现简单。
- 内存回收时不用考虑内存碎片的出现。
缺点: - 可一次性分配的最大内存缩小了一半
Java 的新生代串行垃圾回收器中使用了复制算法的思想
堆大小 = 新生代 + 老年代。其中,堆的大小可以通过参数 –Xms、-Xmx 来指定大小,–XX:NewRatio指定比例默认1:2,-XX:MaxTrnuringThreshold=15指定回收15次之后为老对象
新生代 = Eden + (From Survivor + To Survivor);两块Survivor大小相同、地位相等,且可进行角色互换的空间块, –XX:SurvivorRatio修改比例 默认8:1:1。JVM 每次只会使用 Eden 和其中的一块 Survivor 区域来为对象服务,所以无论什么时候,总是有一块 Survivor 区域是空闲着的。因此,新生代实际可用的内存空间为 9/10 ( 即90% )的新生代空间。
执行过程: 当触发垃圾回收时,将Eden中的对象复制到空闲Survivor中(ex:To),如果是大对象直接进入老年代,当空闲Survivor已满也会直接进入老年代,使用中的Survivor(ex:From)也按改条件进入空闲Survivor
2.3标记-压缩(整理)算法 (Mark-Compact)
jvm新生代使用了复制算法,但是到了老年代由于对象存活率较高,复制效率相对变低。所以老年代一般采用标记-压缩(也叫整理)的算法:它是在标记-清除算法的基础上做了一些优化。也首先需要从根节点开始对所有可达对象做一次标记,但之后,它并不简单地清理未标记的对象,而是将所有的存活对象压缩到内存的一端。之后,清理边界外所有的空间。

优点:
- 避免内存碎片导致无法分配大空间
2.4增量算法 (Incremental Collecting)
增量算法的基本思想是,如果一次性将所有的垃圾进行处理,需要造成系统长时间的停顿,那么就可以让垃圾收集线程和应用程序线程交替执行。每次,垃圾收集线程只收集一小片区域的内存空间,接着切换到应用程序线程。依次反复,直到垃圾收集完成。使用这种方式,由于在垃圾回收过程中,间断性地还执行了应用程序代码,所以能减少系统的停顿时间。但是,因为线程切换和上下文转换的消耗,会使得垃圾回收的总体成本上升,造成系统吞吐量的下降。
2.5分代 (Generational Collecting)
根据对象所在不同的阶段进行不同的回收算法。在新生代中,每次垃圾收集时都会发现有大量对象死去,只有少量存活,因此可选用复制算法来完成收集,而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用标记—清除算法或标记—整理算法来进行回收。
3.垃圾收集器
垃圾收集器是内存回收算法的具体实现,Java虚拟机规范中对垃圾收集器应该如何实现并没有任何规定,因此不同厂商、不同版本的虚拟机所提供的垃圾收集器都可能会有很大的差别。
3.1 串行收集器
Serial收集器:用于新生代,单线程并Stop The World,一般用于单CPU的应用,可以保证较高的吞吐率
Serial Old收集器:
通过指定-XX:+UseSerialGC参数,使用Serial + Serial Old的串行收集器组合进行内存回收。
3.2 并行收集器
ParNew收集器:Serial收集器新生代的多线程实现,注意在进行垃圾回收的时候依然会stop the world,相比较Serial收集器而言它会运行多条进程进行垃圾回收。
Parallel Scavenge收集器:采用复制算法的多线程新生代垃圾回收器,它所关注的目标是吞吐量。吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量=运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间)。停顿时间越短就越适合需要与用户交互的程序,良好的响应速度能够提升用户的体验;而高吞吐量则可以最高效率地利用CPU时间,尽快地完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。
Parallel Old收集器:Parallel Scavenge收集器的老年代版本,采用多线程和”标记-整理”算法。
3.3 并发收集器
CMS(Concurrent Mark Swep)收集器:
CMS收集器一种获取最短回收停顿时间为目标的收集器,使用最广泛。它的收集过程分为四个步骤:
- 初始标记(initial mark)
- 并发标记(concurrent mark)
- 重新标记(remark)
- 并发清除(concurrent sweep)

初始标记和重新标记还是会stop the world,但是在耗费时间更长的并发标记和并发清除两个阶段都可以和用户进程同时工作。不过由于CMS收集器是基于标记清除算法实现的,会导致有大量的空间碎片产生,在为大对象分配内存的时候,往往会出现老年代还有很大的空间剩余,但是无法找到足够大的连续空间来分配当前对象,不得不提前开启一次Full GC。为了解决这个问题,CMS收集器默认提供了一个-XX:+UseCMSCompactAtFullCollection收集开关参数(默认就是开启的),用于在CMS收集器进行FullGC完开启内存碎片的合并整理过程,内存整理的过程是无法并发的,这样内存碎片问题倒是没有了,不过停顿时间不得不变长。虚拟机设计者还提供了另外一个参数-XX:CMSFullGCsBeforeCompaction参数用于设置执行多少次不压缩的FULL GC后跟着来一次带压缩的(默认值为0,表示每次进入Full GC时都进行碎片整理)。
主要优点:并发收集、低停顿。
缺点:
- 对CPU资源非常敏感。在并发阶段,它虽然不会导致用户线程停顿,但是会因为占用了一部分线程而导致应用程序变慢,总吞吐量会降低。
- 无法处理浮动垃圾,可能会出现“Concurrent Mode Failure(并发模式故障)”失败而导致Full GC产生。
浮动垃圾:由于CMS并发清理阶段用户线程还在运行着,伴随着程序运行自然就会有新的垃圾不断产生,这部分垃圾出现的标记过程之后,CMS无法在当次收集中处理掉它们,只好留待下一次GC中再清理。 - CMS是一款“标记–清除”算法实现的收集器,容易出现大量空间碎片。当空间碎片过多,将会给大对象分配带来很大的麻烦,往往会出现老年代还有很大空间剩余,但是无法找到足够大的连续空间来分配当前对象,不得不提前触发一次Full GC。
G1收集器:
G1收集器是一款面向服务端应用的垃圾收集器。HotSpot团队赋予它的使命是在未来替换掉JDK1.5中发布的CMS收集器。与其他GC收集器相比,G1具备如下特点:
- 并行于并发:G1能充分利用CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短stop-The-World停顿时间。部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让java程序继续执行。
- 分代收集:虽然G1可以不需要其他收集器配合就能独立管理整个GC堆,但是还是保留了分代的概念。它能够采用不同的方式去处理新创建的对象和已经存活了一段时间,熬过多次GC的旧对象以获取更好的收集效果。
- 空间整合:与CMS的“标记–清理”算法不同,G1从整体来看是基于“标记整理”算法实现的收集器;从局部上来看是基于“复制”算法实现的。
- 可预测的停顿:这是G1相对于CMS的另一个大优势,降低停顿时间是G1和CMS共同的关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内
G1运作步骤:
- 初始标记
- 并发标记
- 最终标记
- 筛选回收

4.垃圾收集器组合参数
