垃圾收集算法 是内存回收的方法论,而垃圾收集器就是内存回收的具体实现。
我们不同分代之间是有着不同的收集器的,如果两个收集器之间存在连线,那就说明他们可以搭配使用
那么是如何在虚拟机中指定它使用上图中搭配的垃圾收集器呢?主要如下:
-XX:+UseSerialGC 新生代和老年代都用串行收集器,,即 Serial 和 Serial Old
-XX:+UseParNewGC 新生代使用 ParNew,老年代使用 Serial Old
-XX:+UseParallelGC 新生代使用 ParallerGC,老年代使用 Serial Old
-XX:+UseParallerOldGC 新生代使用 ParallerGC(即Parallel Scavenge),老年代使用 Parallel Old
-XX:+UseConcMarkSweepGC 表示新生代使用 ParNew,老年代的用 CMS
-XX:+UseG1GC 表示使用 G1
了解了上次的基本搭配,这里我们可以我们自己电脑JDK上是使用的哪一种垃圾收集器,可以使用这个命令
java -XX:+PrintCommandLineFlags -version
收集器 | 收集对象 | 算法 | 收集器类型 | 说明 | 适用场景 |
---|---|---|---|---|---|
Serial | 新生代 | 复制算法 | 单线程 | 进行垃圾收集时,必须暂停所 有工作线程,直到完成; (stop the world) |
简单高效; 适合内存不大的情况 |
ParNew | 新生代 | 复制算法 | 并行的多线程收集器 | ParNew垃圾收集器是Serial 收集器的多线程版本 |
搭配CMS垃圾回收器的首选 |
Parallel Scavenge 吞吐量优先 |
新生代 | 复制算法 | 并行的多线程收集器 | 类似ParNew,更加关注吞吐 量,达到一个可控制的吞吐量 |
本身是Server级别多CPU机 器上的默认GC方式,主要适 合后台运算不需要太多交 互的任务 |
吞吐量 = 运行用户代码时间/(运行用户代码时间+ 垃圾收集时间)
垃圾收集时间 = 垃圾回收频率 * 单次垃圾回收时间
收集器 | 收集对象 | 算法 | 收集器类型 | 说明 | 适用场景 |
---|---|---|---|---|---|
Serial Old |
老年代 | 标记整理算法 | 单线程 | jdk7/8默认的老生代垃圾回收器 | Client模式下虚拟机使用 |
Parallel Old |
老年代 | 标记整理算法 | 并行的多线程收集器 | Parallel Scavenge收集器的老年代版 本,为了配合Parallel Scavenge的面 向吞吐量的特性而开发的对应组合 |
在注重吞吐量以及CPU 资源敏感的场合采用 |
CMS | 老年代 | 标记清除算法 | 并行与并发收集器 | 尽可能的缩短垃圾收集时用户 线程停止时间;缺点在于: 1、内存碎片 2、需要更多cpu资源 3、浮动垃圾问题,需要更大的堆空间 |
重视服务的响应速度、 系统停顿时间和用户体 验的>互联网网站或者 B/S系统。互联网后端目 前cms是主流垃圾回收器 |
并行: 垃圾收集的多线程的同时进行。
并发: 垃圾收集的多线程和应用的多线程同时进行。
收集器 | 收集对象 | 算法 | 收集器类型 | 说明 | 适用场景 |
---|---|---|---|---|---|
G1 | 跨新生代和老年代 | 标记整理+化整为零 | 并行与并发收集器 | JDK1.7才正式引入,采用分区回 收的思维,基本不牺牲吞吐量的 前提下完成低停顿的内存回收; 可预测的停顿是其最大的优势 |
面向服务端应用 的垃圾回收器, 目标为取代CMS |
ZGC | 跨新生代和老年代 | 动态地创建和销毁 Region;动态地决 定Region的大小 |
并发,只会在枚举 根节点的阶段STW (Stop The World), 因此停顿时间不会 随着堆大小或存活 对象的多少而增加 |
JDK11版本包含一个全新的垃圾 收集器ZGC,它由Oracle开发, 承诺在数TB的堆上具有非常低的 暂停时间。 |
面向服务端应用 的垃圾回收器, 目标为取代G1 |
|ZGC|跨新生代和老年代|动态地创建和销毁Region动态地决定Region的大小|只会在枚举根节点的阶段STW, 因此停顿时间不会随着堆大小或存活对象的多少而增加|JDK11版本包含一个全新的垃圾收集器ZGC,它由Oracle开发,承诺在数TB的堆上具有非常低的暂停时间。||
这里我们先来介绍一下新生代的垃圾收集器,一般从新生代空间(包括 Eden 和 Survivor 区域)回收内存被称为 Minor GC。
这个收集器是一个单线程的收集器,但它的“单线程”的意义并不仅仅说明它只会使用一个CPU或者一条收集线程去完成垃圾收集工作,更重要的是在它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束。
虽然它在进行垃圾收集时,工作线程必须停止,但是它也是有一点优点的,比如在单个CPU的环境下,Serial收集器由于没有线程交互的开销,可以获得最高的单线程收集效率,还有就是在桌面应用的场景下,分配给虚拟机管理的内存一般来说不会很大,停顿时间可以控制在几十毫秒最多一百多毫秒以内。
ParNew收集器其实就是Serial收集器的多线程版本,处理使用多条线程进行垃圾收集之外,其余的行为几乎和Serial收集器一致。在多CPU的环境,停顿时间比Serial垃圾收集器短。
Parallel Scavenge收集器和ParNew收集器的区别是,它是主要关注吞吐量的垃圾收集器,高吞吐量则可以高效率地利用CPU时间,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的任务。
所谓吞吐量就是CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量=运行用户代码时间 /(运行用户代码时间+垃圾收集时间),虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%。
Parallel Scavenge收集器提供了两个参数用于精确控制吞吐量,分别会控制最大垃圾收集停顿时间的-XX:MaxGCPauseMillis
参数以及直接设置吞吐量大小的 -XX:GCTimeRatio
参数。
另外Parallel Scavenge收集器还有一个参数 -XX:+UseAdaptiveSizePolicy
,这是一个开关参数,当这个参数打开之后,就不需要手工指定新生代的大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRatio)、晋升老年代对象年龄(-XX:PretenureSizeThreshold)等细节参数了,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量,这种调节方式称为GC自适应的调节策略。
介绍完了新生代垃圾收集器,让我们再来看看老年代的垃圾回收器,Full GC是清理整个堆空间。
Serial Old收集器是Serial收集器的老年代版本,它同样是一个单线程的收集器,使用“标记-整理”算法。
这个收集器主要的意义就是在于给Client模式下的虚拟机使用。
如果在Server模式下,那么它还有两大作用:
Parallel Old收集器是在Parall Scavenge收集器的老年代版本,使用了多线程和“标记-整理”算法。
在注重吞吐量以及CPU资源敏感的场合,可以优先考虑Parallel Scavenge加上Parallel Old收集器。
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。一般应用在互联网网站或者B/S系统的服务端上,这类应用尤其重视服务器的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。
CMS收集器是基于“标记-清除”算法实现的,它的运行过程相对于前面的几种更为复杂,如下:
由于整个过程中耗时最长的并发标记和并发清除过程收集器线程都可以与用户线程一起工作,所以,从总体上来说,CMS收集器的内存回收过程是与用户线程一起并发执行的。
当然我们的CMS垃圾收集器也是有一些缺点的,如我们上图,CMS收集器运行示意图可以看书,就是在我们CMS在并发清除阶段,用户线程还在运行着,伴随着程序的运行自然就还会新的垃圾不断产生,这一部分垃圾在出现在标记过程之后,CMS无法在本次收集中处理它们,只好留待下一次GC时在处理,这一部分垃圾就成为浮动垃圾。
另外就是由于CMS垃圾收集器中,我们的用户线程也是需要运行的,所以就需要预留有足够的内存空间给用户线程去使用,因此CMS收集器不能像其他收集器那样等老年代几乎完全被填满了在进行收集。在JDK1.5的默认配置下,CMS收集器当老年代使用了68%的空间后就会被激活,这是一个偏保守的设置,如果在应用中老年代增长不是太快,可以适当调高参数 -XX:CMSInitiatingOccupancyFraction
的值来提高触发百分比,以便降低内存回收次数从而获取更好的性能,在JDK 1.6中,CMS收集器的启动阈值已经提升至92%。要是CMS运行期间预留的内存无法满足程序需要,就会出现一次“Concurrent Mode Failure”失败,
这时虚拟机将启动后备预案:临时启用Serial Old收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了。所以说参数-XX:CMSInitiatingOccupancyFraction设置得太高很容易导致大量“Concurrent Mode Failure”失败,性能反而降低。
所以我们从一开始的图中就可以看出在CMS收集器和Serial Old之间是有一条连线的,我们在Serial垃圾收集器中也说了,Serial有一种用途就是作为CMS的后备预案的,在并发收集发生Concurrent Mode Failure时使用。
另外还有就是CMS垃圾收集器采用的是“标记-清除”算法实现的,所以CMS收集结束后会有大量空间碎片产生。空间碎片过多时,将会给大对象分配带来很大麻烦,往往会出现老年代还有很大的空间剩余,但是无法找到足够大的连续空间来分配当前对象,不得不提前触发一次Full GC。
所以为了解决这个问题,CMS收集器提供了一个参数 -XX:+UseCMSCompactAtFullCollection
(默认就是开启的),用于在CMS收集器顶不住要进行FullGC时开启内存碎片的合并整理过程,内存整理的过程是无法并发的,空间碎片问题没有了,但停顿时间不得不变长。
还提供了 -XX:CMSFullGCsBeforeCompaction
参数是用于设置执行多少次不压缩的Full GC后,跟着来一次带压缩的(默认值为0,表示每次进入FullGC时都进行碎片整理)。
G1(Garbage First)特点:
回收Eden区和survivor区,回收后,所有eden区被清空,存在一个survivor区保存了部分数据。老年代区域会增多,因为部分新生代的对象会晋升到老年代。
那么G1垃圾收集器的新生代主要发生在上图的哪一个阶段呢?主要是在发生在初始化阶段,在初始化阶段中仅仅只是标记一下GC Roots能直接关联到的对象,速度很快,产生一个全局停顿,都伴随有一次新生代的GC。
然后再初始化阶段后,会有一个根区域的扫描,就是扫描survivor区可以直接到达的老年代区域。
然后就是并发标记阶段扫描和查找整个堆的存活对象,并标记。我们并发标记阶段是并发的,工作线程也是在运行的,伴随着程序的运行自然就还会新的垃圾不断产生,这里我们又会进行重新标记,会产生全局停顿,对并发标记阶段的结果进行修正。
标记完成后,就是独占清理,这里也会产生全局停顿,对GC回收比例进行排序,供混合收集阶段使用,混合收集就是在我们Region区域中进行部分收集,会优先收集垃圾比例较高的区域,另外混合收集还可能会进行新生代的垃圾收集。
最后就是并发清理,就是识别并清理完全空闲的区域,并发进行。另外在G1中也可能在回收完后也会发生内存不足的的情况,这时也可能进行Full GC回收。
G1垃圾收集器还为提供了几个常用的参数:
关键技术:
目标:
ZGC通过技术手段把STW(Stop The World)的情况控制在仅有一次,就是第一次的初始标记才会发生,这样也就不难理解为什么GC停顿时间不随着堆增大而上升了,再大我也是通过并发的时间去回收了。
另外就是ZGC也是像G1一样划分Region,但更加灵活,G1一开始就把堆划分成固定大小的Region,而ZGC 可以有2MB,32MB,N×2MB 三种Size Groups,动态地创建和销毁Region,动态地决定Region的大小。256k以下的对象分配在Small Page, 4M以下对象在Medium Page,以上在Large Page。所以ZGC能更好的处理大对象的分配。
和G1一样会做内存的压缩,CMS是Mark-Sweep标记过期对象后原地回收,这样就会造成内存碎片,越来越难以找到连续的空间,直到发生Full GC才进行压缩整理。ZGC和G1会将活着的对象都移动到另一个Region,整个回收掉原来的Region。