新生代 | 老年代 | VM选项参数 |
---|---|---|
Serial | Serial Old | -XX:UseSerialGC |
ParNew | Serial Old | -XX:UseParNewGC |
Parallel Scavenge | Serial Old | -XX:UseParallelGC |
Parallel Scavenge | Parallel Old | -XX:UseParallelOldGC |
Parallel Scavenge | CMS/Serial Old | -XX:UseConcMarkSweepGC |
G1 | G1 | -XX:+UseG1GC |
串行收集器组合 Serial + Serial Old(单线程)
并行收集器组合 Parallel Scavenge + Parallel Old(多线程)
(JDK8默认的GC回收器是 Parallel Scavenge 和 Parallel Old)
(JDK9以后默认的GC回收器是 G1)
安全点(safepoint):
程序执行时并非在所有地方都能停顿下来开始GC,只有在到达安全点时才能暂停。安全点的初始目的并不是让其他线程停下,而是找到一个稳定的执行状态。在这个状态下,Java虚拟机的堆栈都不会发生变化。这么一来,垃圾回收器便能够“安全”地执行可达性分析。
安全点的选定基本上是以程序“是否具有让程序长时间执行的特征”为标准进行选定的,“长时间执行”的最明显特征就是指令序列复用,例如方法调用,循环跳转,异常跳转等,具有这些功能的指令才会产生安全点。
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器,因为CMS工作时,GC工作线程与用户线程可以并发执行,以此来达到降低收集停顿时间的目的。
CMS 仅作用于老年代的收集,基于“标记-清除”算法,当老年代使用率达到92%会触发GC
CMS运行过程:
- 初始标记(GCRoots 能直接关联到的对象,速度快,Stop The World 停止所有进程)
- 并发标记(进行GC Root Tracing 可达性分析)
- 重新标记(修正并发标记期间因用户程序继续运作而导致的变动,速度快,Stop The World 停止所有进程)
- 并发清除(与用户进程并发运行)
整个过程中只有两次短暂的暂停,达到了近似并发的目的。
CMS的优点:并发收集,低停顿。
CMS的缺点:1. 对 CPU 资源太敏感
2. CMS 收集器无法处理浮动垃圾。
(所谓的“浮动垃圾”,就是在并发标记阶段,由于用户程序在运行,那么自然就会有新的垃圾产生,这部分垃圾被标记过后,CMS 无法在当次集中处理它们(为什么?原因在于 CMS 是以获取最短停顿时间为目标的,自然不可能在一次垃圾处理过程中花费太多时间),只好在下一次 GC 的时候处理。这部分未处理的垃圾就称为“浮动垃圾”。由于垃圾收集阶段用户线程还需要运行,那就不能等老年代几乎全满了再收集,一般达到 92%时就开始收集,而 CMS 运行期间预留的内存无法满足程序需要,就会出现“Concurrent Mode Failure”,此时将启动备用方案 serial old)
3. 基于“标记-清除”算法,会导致大量的空间碎片的产生。
G1(Garbage-First)收集器重新定义了堆空间,打破了原有的分代模型,将堆分为一个个区域(Region)。这么做的目的是在进行收集时不必在全堆范围内进行,虽然还保留新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,他们都是Region(不需要连续)的集合。
G1 跟踪各个 Region 里面的垃圾堆积价值大小(回收所获得的空间大小以及回收所需的时间),在后台维护一个优先列表,每次优先收集价值最大的 Region(所以叫 GarbageFirst),从而保证了 G1 在有限时间内可以获取尽可能高的收集效率。
G1 的 YoungGC 就是将 E 区和 S 区复制到灰色的空白区。
G1 中有 Humongous 区(巨大区)用于存放比标准块大 50%的对象
G1回收过程:
- 初始标记 (Stop The World)
- 并发标记
- 最终标记 (Stop The World)
- 筛选回收(Stop The World,首先对各个Region的回收价值和成本进行排序,更具用户所期望的GC停顿时间来制定回收计划。因为只回收一部分Region,时间是用户控制的,而且停顿用户线程将大幅提高收集效率)
G1的特点:1. 并行与并发(G1能充分利用多CPU,使用多个CPU来缩短停顿时间)
2. 分代收集(不需要其他收集器配合)
3. 空间整合(G1从整体上看是基于“标记-整理”算法实现的,从局部(两个Region之间)上看是基于“复制”算法实现的。意味着G1运行期间不会产生内存碎片,收集后能提供规整的可用内存。这种特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前触发下一次GC)
4. 可预测的停顿(G1回收的第4步,它是“选择一些内存块”,而不是整代内存来回收,这是G1跟其它GC非常不同的一点,其它GC每次回收都会回收整个Generation的内存(Eden, Old), 而回收内存所需的时间就取决于内存的大小,以及实际垃圾的多少,所以垃圾回收时间是不可控的;而G1每次并不会回收整代内存,到底回收多少内存就看用户配置的暂停时间,配置的时间短就少回收点,配置的时间长就多回收点,伸缩自如。)
(-XX:MaxGCPauseMillis=200 为最大GC暂停时间设置一个指标。这是一个软目标,Java虚拟机将尽最大努力实现它。因此,暂停时间目标有时候可能不会达到。默认值是200毫秒。 )
卡表(Card Table):
在每个分区内部又被分成了若干个大小为512 Byte卡片(Card),标识堆内存最小可用粒度所有分区的卡片将会记录在全局卡片表(Global Card Table)中,分配的对象会占用物理上连续的若干个卡片,当查找对分区内对象的引用时便可通过记录卡片来查找该引用对象。每次对内存的回收,都是对指定分区的卡片进行处理。维护一个全局卡片表,用来存储每张卡的一个标识位,这个标识位代表对应的卡是否可能存在有指向新生代对象的引用。如果可能存在,那么我们就认为这张卡是脏的。在进行Minor GC的时候,我们便可以不用扫描整个老年代,而是在卡表中寻找脏卡,并将脏卡中的对象加入到Minor GC的GCRoots里。当完成所有脏卡的扫描之后,Java虚拟机便会将所有脏卡的标识位清零。
卡表能用于减少老年代的全堆空间扫描,这能很大的提升GC效率。
◆ Java提供了一个为我们各种获取JVM信息的工厂类ManagementFactory,使用可以参考https://blog.csdn.net/dream_broken/article/details/49759043
参考:部分资料来源于网络