垃圾收集器概述

1. 垃圾收集器的功能

从操作系统申请和释放内存; 当应用申请内存时,为其分配内存,判断哪些内存还在被应用使用中,从应用中回收不再使用的内存。

2   垃圾收集器的指标及其种类

2.1 垃圾收集器的指标

目前Java Hotspot提供了不同种类的垃圾收集器:

Serial 收集器 / Parallel 收集器 / CMS收集器 / G1收集器 / ZGC收集器

不同的垃圾收集器有各自的特色,适合不同的应用,他们有各自不同的侧重点;

一般来说 ,衡量一个垃圾收集器,有两个最主要的指标:  最大停顿时间和吞吐率; 此外,内存占用也是一个关注点。这三者是互斥的,很难同时满足。要在这三个方面同时具有卓越表现的“完美”收集器是极其困难甚至是不可能的,一款优秀的收集器通常最多可以同时达成其中的两项。

在以上三个指标里,最大停顿时间是相对最重要的指标。因为吞吐率或者内存占用,可以通过增加(牺牲)硬件来改善。但是最大停顿时间,在某些情况下,随着硬件的增强,反而会更难达到:假设本来只有1G的内存,那么我们只需要管理回收这1G的内存空间,但是当内存增加到10G,回收的时间在固定的算法下大概率会增加。

2.2 垃圾收集器中的理念和技术

在不同的垃圾收集器里,陆续引入了很多理念和技术来优化它的指标:

并行(Parallel):

并行描述的是多条垃圾收集器线程之间的关系,说明同一时间有多条这样的线程在协同工作,通常默认此时用户线程是处于等待状态。除了Serial 收集器,其他的收集器都引用了并行来提高最大停顿时间

并发(Concurrent):

并发描述的是垃圾收集器线程与用户线程之间的关系,说明同一时间垃圾收集器线程与用户线程都在运行。由于用户线程并未被冻结,所以程序仍然能响应服务请求,但由于垃圾收集器线程占用了一部分系统资源,此时应用程序的处理的吞吐量将受到一定影响; CMS收集器 / G1收集器 / ZGC收集器都引入了并发,并且努力使时间占比长的task,尽可能的实现并发,不过目前的话,没有任何一个款收集器,是百分百并发的;假设百分百并发能够实现,最大停顿时间就不存在了。

很难做到百分百并发的原因主要在于两个特殊的阶段:

1)判断垃圾过程中的并发:以CMS为例,它采用可达性分析算法来判断哪些是需要回收的垃圾,在初始标记阶段(标记一下GC Roots能直接关联到的对像)以及重新标记阶段(修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录)这两个阶段中,无法做到并发,因为如果和用户进程并行的话,GC Roots变动或者新产生的垃圾,或者垃圾对象又有了引用,这几种情况没办法获知。

2)活对象移动过程中的并发:为了防止内存碎片,往往会发生对象的移动(它的引用会发生变化),如果用户进程访问同步运行,那么当访问到这个发生移动的对象,该如何处理? ZGC引入了染色指针技术解决了这个问题,使得这个过程也可以并发。

分代(Generations):

大部分的垃圾收集器都采用了这个理念(Serial 收集器 / Parallel 收集器 / CMS收集器 / G1收集器);收集器将Java堆划分出不同的区域,然后将回收对象依据其年龄(年龄即对象熬过垃圾收集过程的次数)分配到不同的区域之中存储。显而易见,如果一个区域中大多数对象都是朝生夕灭,难以熬过垃圾收集过程的话,那么把它们集中放在一起,每次回收时只关注如何保留少量存活而不是去标记那些大量将要被回收的对象,就能以较低代价回收到大量的空间;如果剩下的都是难以消亡的对象,那把它们集中放在一块,虚拟机便可以使用较低的频率来回收这个区域,这就同时兼顾了垃圾收集的时间开销和内存的空间有效利用。

Region

这是在G1收集器中首次引入的概念,它将连续的Java堆划分为多个大小相等的独立区域(Region),把Region作为单次回收的最小单元,即每次收集到的内存空间都是Region大小的整数倍,这样可以有计划地避免在整个Java堆中进行全区域的垃圾收集。更具体的处理思路是让G1收集器去跟踪各个Region里面的垃圾的“价值”大小,价值即回收所获得的空间大小以及回收所需时间的经验值,然后在后台维护一个优先级列表,每次根据用户设定允许的收集停顿时间,优先处理回收价值收益最大的那些Region,这也就是“Garbage First”名字的由来。

染色指针技术:

这是在ZGC收集器中引入的概念,

2.3 垃圾收集器的选择

正对不同的系统和不同的需求,根据  JDK11的官方指导:收集器的选择,有以下的选择思路:

1)如果应用的内存比较小,比如100MB,那么可以采用Serial 收集器

2)如果应用运行在一个单cpu的系统上,而且没有停顿时间的要求,那么可以采用Serial 收集器

3)如果希望应用可以达到很高的性能,并且对停顿时间没有很高的要求(大于1s或者更长都可以接受),那么可以让VM自己选择收集器或者选择用Parallel 收集器

4)如果认为系统响应时间(要小于1s)的重要性高于吞吐率,那么可以选择CMS收集器 或者 G1收集器

5)如果响应时间是最关注的指标,并且系统有一个很大的堆内存,那么可以使用/ ZGC收集器

其实目前来说,G1也可以适用于很大的堆内存,但是随着ZGC的成熟,它会慢慢地取代G1(就像G1取代CMS一样),所以在官方的文档中,当应用的堆内存很大时,已经推荐ZGC。

 

参考文档:

深入理解java虚拟机 JVM高级特性与最佳实践(第三版)

https://docs.oracle.com/en/java/javase/11/gctuning/

 

posted on 2020-12-30 16:07  cherryjing0629  阅读(197)  评论(0编辑  收藏  举报

导航