jvm-垃圾收集器
如果说垃圾搜索算法和垃圾清理算法是收集垃圾的理论方法,那么垃圾收集器就是这种理论方法的具体实现,实际上它就是一个程序代码而已,会采取相应的算法进行垃圾回收。
垃圾收集器分类
常见的垃圾收集器如下:
![image-20201022154853694](https://i-blog.csdnimg.cn/blog_migrate/b07cb63668df4734908565647ed5f8e9.png)
Serial收集器(新老两个版本)
Serial 收集器在新生代和老年代都有对应的版本,除了收集算法不同,两个版本并没有其他差异。
- Serial 新生代收集器采用的是复制算法。
- Serial Old 老年代采用的是标记 - 整理算法。
ParNew (Serial的升级版,多线程)
ParNew收集器就是Serial收集器的多线程版本,它也是一个新生代收集器**。**ParNew收集器其实就是Serial收集器的多线程版本,除了使用多条线程进行垃圾收集外,其余行为和Serial收集器完全一样,包括Serial收集器可用的所有控制参数、收集算法、Stop The world、对象分配规则、回收策略等都一样。在实现上也共用了相当多的代码。
ParNew 收集器在单核CPU的环境中绝对不会有比Serial收集器有更好的效果,甚至由于存在线程交互的开销,该收集器在通过超线程技术实现的两个CPU的环境中都不能百分之百地保证可以超越。在多核CPU环境下,随着CPU的数量增加,它对于GC时系统资源的有效利用是很有好处的。采用复制算法。
Parallel 收集器
Parallel 有Parallel Scavenge和Parallel Old两个版本,Parallel Scavenge是新生代收集器,Parallel Old是老年代收集器,这两个搭配使用,除了收集算法不同,其他一样,Parallel Old 老年代采用的是标记 - 整理算法,Parallel Scavenge采用复制算法。
Parallel 收集器和ParNew类似,也是并行多线程收集。与ParNew最大的不同,它关注的是垃圾回收的吞吐量,同时也具有自适应调节策略,当设置好最大垃圾收集停顿时间和垃圾收集时间占总时间的比率这些参数后,收集器会自动向这一目标靠拢,不需要我们关注更多细节。
另外Parallel Scavenge无法与cms收集器搭配使用,因为Parallel 和G1收集器的底层代码框架和其他收集器不一样,其他收集器底层共用了很多代码。
![image-20201022170932685](https://gitee.com/jianxiaojianghu/cloudimage/raw/master/img/20201022170932.png)
CMS(Concurrent Mark Sweep)收集器
CMS是HotSpot在JDK5推出的第一款真正意义上的并发(Concurrent)收集器,第一次实现了让垃圾收集线程与用户线程(基本上)同时工作,它关注的是垃圾回收最短的停顿时间(低停顿),在老年代并不频繁GC的场景下,是比较适用的。Serial Old收集器作为其备选方案。采用标记-清除算法 (不进行压缩操作,产生内存碎片)。
比前面几种收集器更复杂,可以分为4个步骤:
1、初始标记(initial mark)
- 单线程执行
- 需要“Stop The World”
- 但仅仅把GC Roots的直接关联可达的对象给标记一下,由于直接关联对象比较小,所以这里的速度非常快
2、并发标记(concurrent mark)
- 对于初始标记过程所标记的初始标记对象,进行并发追踪标记,
- 此时其他线程仍可以继续工作。
- 此处时间较长,但不停顿。
- 并不能保证可以标记出所有的存活对象;
3、重新标记(remark)
- 在并发标记的过程中,由于可能还会产生新的垃圾,所以此时需要重新标记新产生的垃圾。
- 此处执行并行标记,与用户线程不并发,所以依然是“Stop The World”,
- 且停顿时间比初始标记稍长,但远比并发标记短。
4、并发清除(concurrent sweep)
- 并发清除之前所标记的垃圾。
- 其他用户线程仍可以工作,不需要停顿。
Tips:其中,初始标记和并发标记仍然需要Stop the World、初始标记仅仅标记一下GC Roots能直接关联到的对象,速度很快,并发标记就是进行GC RootsTracing的过程,而重新标记阶段则是为了修正并发标记期间因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段长,但远比并发标记的时间短。
由于整个过程中耗时最长的并发标记和并发清除过程收集器线程都可以与用户线程一起工作,所以整体上说,CMS收集器的内存回收过程是与用户线程一共并发执行的。
优点:响应时间快,适合其注重服务的响应速度,希望系统停顿时间最短的场景。
缺点:
1.对CPU资源非常敏感,CMS默认启动的回收线程数是(CPU数量+3)/4,也就是当CPU在4个以上时,并发回收时垃圾收集线程不少于25%的CPU资源,并且随着CPU数量的增加而下降。
但是当CPU不足4个时(比如2个),CMS对用户程序的影响就可能变得很大,如果本来CPU负载就比较大,还要分出一半的运算能力去执行收集器线程,就可能导致用户程序的执行速度忽然降低了50%,其实也让人无法接受。
并发收集虽然不会暂停用户线程,但因为占用一部分CPU资源,还是会导致应用程序变慢,总吞吐量降低。
2.会产生浮动垃圾,因为是并发清理,所以在这个过程中会产生垃圾对象,而这部分需要下次才能回收。
3.如果CMS运行期间预留的内存无法满足程序需要,就会出现一次“Concurrent Mode Failure”失败,这时虚拟机将启动后备预案:**临时启用Serial Old收集器来重新进行老年代的垃圾收集,这样会导致另一次Full GC****的产生。**这样停顿时间就更长了,代价会更大,所以 "-XX:CMSInitiatingOccupancyFraction"不能设置得太大。
4.这个问题并不是CMS的问题,而是算法的问题。由于CMS基于"标记-清除"算法,清除后不进行压缩操作,所以会产生碎片
G1收集器
- G1(Garbage - First)名称的由来是G1跟踪各个Region里面的垃圾堆的价值大小(回收所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region。
- G1(Garbage-First)是JDK7-u4才推出商用的收集器;
注意:G1与前面的垃圾收集器有很大不同,它把新生代、老年代的划分取消了!
这样我们再也不用单独的空间对每个代进行设置了,不用担心每个代内存是否足够。
七种经典垃圾收集器组合关系
七种经典垃圾收集器总结
新年代 | 老年代 | 其它 |
Serial | Serial Old | |
Serial | CMS | 当 CMS回收失败时, 备选 Serial Old GC |
ParNew | Serial Old | |
ParNew | CMS | 当 CMS回收失败时, 备选 Serial Old GC |
Parallel Scavenge | Serial Old | |
Parallel Scavenge | Parallel Old | |
G1 | G1 |
垃圾收集器 | 类型 | 作用域 | 使用算法 | 特点 | 适用场景 |
---|---|---|---|---|---|
Serial | 串行回收 | 新生代 | 复制算法 | 响应速度优先 | 适用于单核 CPU环境下的 Client模式 |
Serial Old | 串行回收 | 老年代 | 标记-压缩算法 | 响应速度优先 | 适用于单核 CPU环境下的 Client模式 |
ParNew | 并行回收 | 新生代 | 复制算法 | 响应速度优先 | 多核 CPU环境中 Server模式下与 CMS配合使用 |
Parallel Scavenge | 并行回收 | 新生代 | 复制算法 | 吞吐量优先 | 适用于后台运算, 而交互少的场景 |
Parallel Old | 并行回收 | 老年代 | 标记-压缩算法 | 吞吐量优先 | 适用于后台运算, 而交互少的场景 |
CMS(Concurrent Mark-Sweep) | 并发回收 | 老年代 | 标记-清除算法 | 响应速度优先 | 适用于B/S业务, 也就是交互多的场景 |
G1(Garbage-First) | 并发,并行回收(此收集器后期优化后并行方式同时存在) | 新生代& 老年代(整堆收集器) | 复制算法& 标记-压缩算法 | 响应速度优先 | 面向服务端的应用 |