垃圾收集器回收种类 以及七种垃圾收集器
垃圾收集器回收种类
垃圾收集器是垃圾回收算法的具体实现
串行垃圾回收器(Serial)
它为单线程环境设计且只使用一个线程进行垃圾回收,会暂停用户线程
(并行垃圾回收器)Parallel
多个垃圾收集器并行工作,此时用户线程是暂停的
并发垃圾回收器(CMS)
用户线程和垃圾收集线程同时执行(不一定是并行,可能是交替执行,不需要停顿用户线程)
G1垃圾回收器
G1垃圾回收器将堆内存分割成不同的区域然后并发的对其进行垃圾回收
七种垃圾收集器
Serial
Serial,最基本最老的收集器。其存在的主要问题是"Stop The World",即在垃圾回收的时候,必须要暂停所有的其他工作线程,直到收集完成。此外在工作的时候,其垃圾收集线程是单线程的。
ParNew
ParNew 其实就是 Serial 的多线程版本。其除了垃圾收集线程是多线程意外,其他功能与 Serial 一样,如收集算法、Stop The World 的特性、对象分配规则、回收策略等。
Parallel Scavenge
Parallel Scavenge 大多数特性与 ParNew 相似,其最主要的特点是其设计的目标是达到一个 可控制的吞吐量,所谓吞吐量就是 CPU 用于运行用户代码的时间和 CPU 总消耗时间的比值,即 吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间)。高吞吐量意味着高效率地利用 CPU 时间,尽快完成程序计算任务,适合在后台运算而不需要太多交互的任务。
Serial Old
其是 Serial 的老年代版本,其他特性和 Serial 类似,不多赘述。
Parallel Old
其是 Parallel Scavenge 的老年代版本,适合跟 Parallel Scavenge 一起组成一个 吞吐量优先 的收集组合。
CMS
CMS的全称为 Concurrent Mark Sweep,其设计理念是获取最短回收停顿时间。其回收步骤为
- 初始标记
- 并发标记
- 重新标记
- 并发清除
初始标记是简单标记一下 GC Roots 能关联到的对象,因此速度很快。并发标记则需要进行 GC Roots Tracing,重新标记是为了修正在并发标记期间因用户程序继续运行而导致标记产生变动的对象,最终执行并发清除过程。
在收集过程中,只有初始标记和重新标记需要 Stop The World, 耗时较多。而耗时较多的并发标记和并发清除阶段都是可以和用户线程一起执行的,因此其可以大大减少停顿时间。
从CMS的执行模型中,我们也可以看到其拥有以下缺点。
首先我们看到整个 GC 在第 4步是并发清除的,那么在清除过程中就会产生 浮动垃圾 ,因为此时用户线程还在继续执行,因此必然会产生新的垃圾,而这些垃圾因为已经错过了这次的标记,就需要等到下次才能被回收。
此外也正是因为并发清除的原因, JVM 需要为用户运行过程产生的对象预留空间,那么这个度又该如何把握呢?如果预留的少了,就会出现 "Concurrent Mode Failure" 失败,此时 JVM 会启用后备方案:Serial Old,那么就会导致较长时间的停顿。如果预留的多了,那么就会频繁地进行 Full GC,影响性能。
并且因为清除阶段采用的是并发清除,那么用户线程因为在执行的原因,就必然只能采用 标记-清除算法 ,这样就会导致产生大量的内存碎片。
最后因为 CMS 是一个针对并发模型的实现,那么在并发阶段就必然会占用 CPU 时间,这就必然会导致应用程序变慢,因为这个是所有针对并发进行设计的程序都会有的问题。
G1
Garbage-First 算法的核心观念是将堆划分为多个大小相等的独立区域——Region(还保留着新生代和老年代,但其不再是物理隔离的,而是一系列region的集合)。
在程序运行过程中,G1 会维护一个 优先级列表,里面记录着各个 Region 的垃圾堆积价值(回收所获得大小和回收所需时间的比值),这样在每次回收的时候,根据 允许的回收时间,优先回收价值最大的 Region,通过这样,我们的 GC 时间就是可预测的。
但是听着将堆划分为多个 Region 很简单,在实际情况下,我们该如何保证 Region 以及其中对象的独立性呢?
实际上,Region 不可能是独立的,因为一个对象分配到这个 Region 上,但其可能会被堆上的任一个引用所关联。这样在进行可达性分析的时候,难道意味着要进行 全堆扫描 吗?如果这样的话,性能又该如何保证呢?
因此 G1 使用了 Remembered Set 来避免这一现象。在程序对 Reference 类型数据进行写操作时,其会产生一个 Write Barrier 暂时中断写操作,来检查 Reference 引用对象是否处于不同的 Region 中,如果是,则通过 CardTable 将相关引用信息记录到对象所属 Region 的 Remembered Set 中,这样在进行内存回收,可达性分析的时候通过 Remembered Set 则可以保证不对全堆扫描也不会有遗漏。
G1 详细的执行流程如下:
- 初始标记
- 并发标记
- 最终标记
- 筛选回收
粗略来看,G1 的前 3个步骤和 CMS 十分类似。但其内部还是有些不一样。
首先是初始标记阶段,G1 和 CMS 类似,也只是标记一下 GC Roots 能直接关联到的对象,但其还修改了 TAMS(Next Top at Mark Start)的值,让下一阶段并发标记时,用户线程可以正确的在可用 Region 中创建对象。并发标记阶段和 CMS 一样,但将这段时间对象变化记录存储在 Remembered Set Log 中;在最终标记阶段,将 Remembered Set Log 合并到 Remembered Set 中,最终在筛选回收阶段首先对 各个 Region 的价值进行排序,然后根据用户设置的回收时间来制定回收计划。
CMS和G1的对比:
G1是为了替代CMS而存在的。在以下方面表现的更出色:
G1是一个有整理内存过程的垃圾收集器,不会产生很多的内存碎片
G1的STW更可控,G1在停顿时间尚增加了预测机制,用户可以指定期望停顿时间
※G1底层原理
对象特别大直接在分给多个区域来装 :humongous区域 (如果一个对象占用的空间超过了分区容量50%以上,G1收集器就认为这是个巨型对象,这些对象默认直接会被分配到老年代,但是如果他是一个但其存在的巨型对象,G1就会划分一个humongous区域来进行存放,如果一个H区放不下,就会去找连续的H区,但是找不到就会不得不进行FullGC)
在非G1收集器是如何装大对象得?
G1算法将堆分为诺干个区域,它们任然属于分代收集器
这些区域的一部分包含新生代,新生代的垃圾收集依然采用暂停所有应用线程的方式,将存活对象拷贝到老年代或者Survivor空间。
这些区域一部分包含老年代,G1收集器通过将对象从一个区域复制到另外一个区域,完成了清理工作。这就意味着,在正常处理过程中,G1完成了堆的压缩,至少是部分堆的压缩,这样也就不会有CMS内存碎片化的问题了
回收后
查看默认的垃圾回收器
jdk8
-XX:+PrintCommandLineFlags -version
Jdk8的默认垃圾收集器
Paraller收集器