一、 垃圾收集算法
1. 标记-清理算法
“标记-清除”(Mark-Sweep)算法:算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。
缺陷:1)效率问题,标记和清除两个过程的效率都不高;
2)标记清除之后会产生大量不连续的内存碎片。空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
2. 复制算法
为了解决效率问题。
“复制”(Copying)算法:将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。
缺陷:将内存缩小为了原来的一半。
在对象存活率较高时就要进行较多的复制操作,效率将会变低。如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,
所以在老年代一般不能直接选用这种算法。
优点:每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况。实现简单,运行高效。
3. 标记-整理算法
“标记-整理”(Mark-Compact)算法:标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
4. 分代收集算法
“分代收集”(Generational Collection)算法:把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。
在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。
老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用“标记—清理”或者“标记—整理”算法来进行回收。
二、 垃圾收集器
1. Serial收集器(单线程、 复制算法)
Serial是最基本垃圾收集器,使用复制算法。Serial 是一个单线程的收集器,它不但只会使用一个 CPU 或一条线程去完成垃圾收集工作,并且在进行垃圾收集的同时,必须暂停其他所有的工作线程,直到垃圾收集结束。
优点:简单高效,对于限定单个 CPU 环境来说,没有线程交互的开销,可以获得最高的单线程垃圾收集效率。因此 Serial垃圾收集器依然是 java 虚拟机运行在 Client 模式下默认的新生代垃圾收集器。
2. ParNew收集器 (Serial+多线程)
ParNew 垃圾收集器其实是 Serial 收集器的多线程版本,也使用复制算法。
ParNew 收集器默认开启和 CPU 数目相同的线程数,可以通过-XX:ParallelGCThreads 参数来限制垃圾收集器的线程数。
ParNew虽然是除了多线程外和Serial收集器几乎完全一样,但是ParNew垃圾收集器是很多java虚拟机运行在 Server 模式下新生代的默认垃圾收集器。
3. Parallel Scavenge收集器(多线程复制算法、高效)
Parallel Scavenge收集器是一个新生代收集器,它也是使用复制算法的收集器,又是并行的多线程收集器。
它重点关注的是程序达到一个可控制的吞吐量。CMS等收集器的关注点是尽可能地缩短垃圾收集时用户线程的停顿时间。
吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)
主要适合在后台运算而不需要太多交互的任务。
自适应调节策略也是 ParallelScavenge 收集器与 ParNew 收集器的一个重要区别。
4. Serial Old收集器(单线程标记整理算法)
Serial Old 是 Serial 垃圾收集器年老代版本,它同样是个单线程的收集器,使用标记-整理算法,这个收集器也主要是运行在 Client 默认的 java 虚拟机默认的年老代垃圾收集器。
在 Server 模式下,主要有两个用途:
1. 在 JDK1.5 之前版本中与新生代的 Parallel Scavenge 收集器搭配使用。
2. 作为年老代中使用 CMS 收集器的后备垃圾收集方案。
5. Parallel Old收集器(多线程标记整理算法)
Parallel Old收集器是Parallel Scavenge的年老代版本,使用多线程的标记-整理算法,在JDK1.6才开始提供。
Parallel Old 正是为了在年老代同样提供吞吐量优先的垃圾收集器,如果系统对吞吐量要求比较高,可以优先考虑新生代 Parallel Scavenge和年老代 Parallel Old 收集器的搭配策略。
6. CMS收集器(并发收集、低停顿,标记—清除)
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。
基于“标记—清除”算法实现
运作过程:
1)初始标记:标记一下GC Roots能直接关联到的对象,速度很快(“Stop The World”)
2)并发标记:进行 GC Roots 跟踪的过程,和用户线程一起工作,不需要暂停工作线程。
3)重新标记:修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短。(“Stop The World”)
4)并发清除:清除 GC Roots 不可达对象,和用户线程一起工作,不需要暂停工作线程。
优点:并发收集、低停顿。
缺点:
1)CMS收集器对CPU资源非常敏感。
2)CMS收集器无法处理浮动垃圾(由于CMS并发清理阶段用户线程还在运行着,还会有新的垃圾不断产生,这一部分垃圾出现在标记过程之后,只好留待下一次GC时再清理掉,这一部分垃圾就称为“浮动垃圾”)。
可能出现“Concurrent Mode Failure”失败而导致另一次Full GC的产生。
由于在垃圾收集阶段用户线程还需要运行,那也就还需要预留有足够的内存空间给用户线程使用,因此CMS收集器不能像其他收集器那样等到老年代几乎完全被填满了再进行收集,需要预留一部分空间提供并发
收集时的程序运作使用。要是CMS运行期间预留的内存无法满足程序需要,就会出现一次“Concurrent Mode Failure”失败,这时虚拟机将启动后备预案:临时启用Serial Old收集器来重新进行老年代的垃圾收集。
3)CMS是一款基于“标记—清除”算法实现的收集器。会有大量空间碎片产生。
7. G1收集器
将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region(不需要连续)的集合。
特点:
1)分代收集:不需要其他收集器配合就能独立管理整个GC堆。
2)空间整合:G1从整体来看是基于“标记—整理”算法实现的收集器,从局部(两个Region之间)上来看是基于“复制”算法实现的。
3)可预测的停顿:G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒。
运作过程:
1)初始标记:标记一下GC Roots能直接关联到的对象,并且修改TAMS(Next Top at Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可用的Region中创建新对象,耗时很短(“Stop The World”)。
2)并发标记:从GC Root开始对堆中对象进行可达性分析,找出存活的对象,这阶段耗时较长。用户程序并发执行。
3)最终标记:修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录。(“Stop The World”)
4)筛选回收:首先对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划,只回收一部分Region,时间是用户可控制的,而且停顿用户线程将大幅提高收集效率。(“Stop The World”)
原文:https://www.cnblogs.com/wq-blogs/p/11964533.html