前言
我们今天一起来聊一聊关于垃圾收集的细节问题。垃圾收集是通过何种方式减少stop the world?这将是垃圾回收的重点内容。
什么是分代回收?
什么是分代回收,初次接触的同学肯定是很懵的。还记得我们前面在介绍使用jvisualvm
工具的时候,从它给我们反馈的视图上看到,有几个不同的数据块,且是动态的,如下图红圈的部分:
从左到右分别是:
- Metaspace :元空间
- Old :老年代
- Eden + S0 + S1 : 这三个加在一起称为年轻代
到目前为止,市面上常见的垃圾收集器,都是按照这种分代回收的方式进行垃圾回收的,但是其内部的实现方式却有很大的差别。
本章我们的重点是 老年代 和 年轻代 。
通过上图我们发现,年轻代有三部分组成:
- Eden :通常称为Eden区,翻译过来也可以称为伊甸园区。
- S0 和 S1:通常称为survivor区,翻译过来称为幸存者区,由幸存者0和幸存者1所组成。
为什么采用分代回收?
绝大部分的对象,它们的生命周期非常短暂,甚至绝大多数都是临时对象,垃圾收集器的设计需要适应这种情况。
我们都知道对象创建,存放在堆中,而不论是老年代还是年轻代都是堆中的一部分。
年轻代回收
当对象被创建后,会被分配至年轻代,随着对象的增加,年轻代会被占满,此时将会停止全部的应用线程,并进行垃圾回收,没有被使用的对象会被回收,仍然被使用的对象将会被移动到其他的地方。这种操作就是MinorGC
,年轻代回收。
使用分代算法的最根本原因,是为了尽量的减少垃圾回收造成的停顿,我们可以从下面两个方面考虑:
- 新生代是堆的一部分,仅处理一部分空间,一定比整个堆空间的时间要短,停顿时间也就短。但是我们一定会想到,停顿的频率增加了。
- 年轻代的空间分配方式,对性能有影响。年轻代中,eden占据大部分空间,而S0和S1平分剩余空间。对象首次创建会在Eden中,经过一次垃圾回收时,Eden被清空,未使用的对象被回收,仍然使用的对象进入Survior或老年代。Eden区的清空操作,相当于进行了一次
压缩整理
。
即使是年轻代的回收,仍然存在时空停顿
。
老年代回收
前面提到,除了在Eden可能将对象移动到老年代当中,对于在Survior当中没有被回收的对象,最终也会移动到老年代当中。当老年代被占满时,会停止所有的应用线程,找到不再使用的对象进行垃圾回收。这个过程将会停顿很长时间,我们称之为Full GC
。
更加厉害的回收方式
前面的描述其实都是较为简单的垃圾收集器,在停止应用线程后去发现不再使用的对象,进行回收。
然而实际上,通过一些定制,和复杂的计算,我们可以在应用线程运行时去找到不再使用的对象。在前一篇文章一笔带过的CMS和G1收集器,就是如此。他们不需要停止应用线程就可以找到不再使用的对象,所以它们也叫做concurrent垃圾收集器
。
垃圾在查找的时候将会占用很多时间,当然查找算法将是下一章节我们需要学习的内容。然而concurrent垃圾收集器可以尽可能的减少应用的停顿时间,它们也可以称为低停顿收集器
。
这种垃圾收集器缩减应用的停顿时间,其代价是占用更多的CPU。即使是G1和CMS也会遇到长时间的Full GC,这将是我们需要针对实际环境调优的方向。
垃圾收集器的权衡
前面简单描述了不同垃圾收集器的垃圾收集方式,以及造成的影响。但是在我们选择垃圾收集器的过程中还是需要一定的权衡,才能使其发挥最佳的性能。
我们需要考虑的重点就是:
吞吐量
:如果你的系统需要大批量的处理数据等,换句话说,不要求每次响应时间最快,而平均响应时间更加重要,Parallel收集器也许会有不错的表现。响应时间
:简单来说,如果你想要你的接口获得更快的响应时间,那么concurrent收集器将是更好的选择。CPU性能
:使用current收集器势必要消耗更多的CPU资源。
到此这篇关于java性能优化之分代回收的文章就介绍到这了,更多相关java性能优化内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!