java性能优化
我犟不过你 人气:0前言
我们今天一起来聊一聊关于垃圾收集的细节问题。垃圾收集是通过何种方式减少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资源。
加载全部内容