JavaJAVA

JVM(十一)内存与垃圾回收|垃圾回收器

2020-08-14  本文已影响0人  TiaNa_na

本文介绍垃圾回收器。

目录
 1 GC的分类与性能指标
  2 评估GC的性能指标
  2.1 吞吐量
  2.2 暂停时间
   2.3 吞吐量VS暂停时间
 3 不同的垃圾回收器概述
   3.1 垃圾收集器发展史
   3.2 7款经典的垃圾回收器
   3.3 7款经典的垃圾收集器与垃圾分代之间的关系
   3.4 垃圾收集器的组合关系
   3.5 查看默认的垃圾收集器
 4 Serial回收器:串行回收
 5 ParNew回收器:并行回收
 6 Parallel回收器:吞吐量优先
 7 CMS回收器:低延迟
 8 G1回收器:区域化分代式
   8.1 为什么要发布Garbage First (G1)GC?
   8.2 为什么叫做Garbage First (G1)GC?
   8.3 优势(特点)
   8.4 缺点
   8.5 参数设置
   8.6 G1回收器的常见操作步骤
   8.7 适用场景
   8.8 region分区
   8.9 G1回收器垃圾回收过程
   8.10 记忆集与写屏障
   8.11 G1回收过程详解
   8.12 优化建议
 9 垃圾回收器总结
 10 GC日志分析
 11 垃圾回收器的新发展


1 GC的分类与性能指标


按线程数分,可以分为串行垃圾回收器和并行垃圾回收器

按照工作模式分,可以分为并发式垃圾回收器和独占式垃圾回收器

按碎片处理方式分,可分为压缩式垃圾回收器和非压缩式垃圾回收器

按工作的内存区间分,又可分为年轻代垃圾回收器和老年代垃圾回收器

2 评估GC的性能指标


2.1 吞吐量

2.2 暂停时间

2.3 吞吐量VS暂停时间

3 不同的垃圾回收器概述


3.1 垃圾收集器发展史

有了虚拟机,就一定需要收集垃圾的机制,这就是Garbage Collection, 对应的产品我们称为Garbage Collector.

3.2 7款经典的垃圾收集器

3.3 7款经典的垃圾收集器与垃圾分代之间的关系

3.4 垃圾收集器的组合关系

   为什么要有很多收集器个不够吗? 因为Java的使用场景很多, 移动端,服务器等。所以就需要针对不同的场景,提供不同的垃圾收集器,提高垃圾收集的性能。
  虽然我们会对各个收集器进行比较,但并非为了挑选一个最好的收集器出来。没有一种放之四海皆准、任何场景下都适用的完美收集器存在,更加没有万能的收集器。所以我们选择的只是对具体应用最合适的收集器

3.5 查看默认的垃圾收集器

/**
 *  -XX:+PrintCommandLineFlags 
 *
 * 修改默认GC
 *  -XX:+UseSerialGC:表明新生代使用Serial GC ,同时老年代使用Serial Old GC
 *
 *  -XX:+UseParNewGC:标明新生代使用ParNew GC
 *
 *  -XX:+UseParallelGC:表明新生代使用Parallel GC
 *  -XX:+UseParallelOldGC : 表明老年代使用 Parallel Old GC
 *  说明:二者可以相互激活
 *
 *  -XX:+UseConcMarkSweepGC:表明老年代使用CMS GC。同时,年轻代会触发对ParNew 的使用
 */
public class GCUseTest {
    public static void main(String[] args) {
        ArrayList<byte[]> list = new ArrayList<>();

        while(true){
            byte[] arr = new byte[100];
            list.add(arr);
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

4 Serial回收器:串行回收


优势

总结

5 ParNew回收器:并行回收


6 Parallel回收器:吞吐量优先


参数配置

7 CMS回收器:低延迟


垃圾收集过程

  CMS整个过程比之前的收集器要复杂,整个过程分为4个主要阶段,即初始标记阶段并发标记阶段重新标记阶段并发清除阶段

  尽管CMS收集器采用的是并发回收(非独占式),但是在其初始化标记和再次标记这两个阶段中仍然需要执行“Stop-the-World”机制暂停程序中的工作线程,不过暂停时间并不会太长,因此可以说明目前所有的垃圾收集器都做不到完全不需要“Stop-the-World”,只是尽可能地缩短暂停时间
  由于最耗费时间的并发标记与并发清除阶段都不需要暂停工作,所以整体的回收是低停顿的。
  另外,由于在垃圾收集阶段用户线程没有中断,所以在CMS回收过程中,还应该确保应用程序用户线程有足够的内存可用。因此,CMS收集器不能像其他收集器那样等到老年代几乎完全被填满了再进行收集,而是当堆内存使用率达到某一阈值时,便开始进行回收,以确保应用程序在CMS工作过程中依然有足够的空间支持应用程序运行。要是CMS运行期间预留的内存无法满足程序需要,就会出现一次“Concurrent Mode Failure”失败,这时虚拟机将启动后备预案:临时启用Serial Old收集器来重新进行老年代的垃圾收集,这样停顿时间就很长了。
  CMS收集器的垃圾收集算法采用的是标记--清除算法,这意味着每次执行完内存回收后,由于被执行内存回收的无用对象所占用的内存空间极有可能是不连续的一些内存块,不可避免地将会产生一些内存碎片。 那么CMS在为新对象分配内存空间时,将无法使用指针碰撞(Bump the Pointer) 技术,而只能够选择空闲列表(Free List) 执行内存分配。

有人会觉得既然Mark Sweep会造成内存碎片,那么为什么不把算法换成Mark Compact呢?
因为当并发清除的时候,用Compact整理内存的话,原来的用户线程使用的内存还怎么用呢?要保证用户线程能继续执行,前提的它运行的资源不受影响嘛。Mark Compact更适合“Stop the World”这种场景”下使用

CMS的优点 .

CMS的弊端

参数设置

小结
如果你想要最小化地使用内存和并行开销,请选Serial GC;
如果你想要最大化应用程序的吞吐量,请选Parallel GC;
如果你想要最小化GC的中断或停顿时间,请选CMS GC。

JDK 后续版本中CMS的变化

8 G1回收器:区域化分代式


8.1 为什么要发布Garbage First (G1)GC?

  原因就在于应用程序所应对的业务越来越庞大、复杂,用户越来越多,没有GC就不能保证应用程序正常进行,而经常造成STW的GC又跟不上实际的需求,所以才会不断地尝试对GC进行优化。G1(Garbage-First) 垃圾回收器是在Java7 update4之后引入的一个新的垃圾回收器,是当今收集器技术发展的最前沿成果之一。
  与此同时,为了适应现在不断扩大的内存和不断增加的处理器数量,进一步降低暂停时间(pause time) ,同时兼顾良好的吞吐量,官方给G1设定的目标是在延迟可控的情况下获得尽可能高的吞吐量,所以才担当起“全功能收集器”的重任与期望

8.2 为什么叫做Garbage First (G1)呢?

8.3 优势(特点)

与其他GC收集器相比,G1使用了全新的分区算法,其特点如下所示:

8.4 缺点

8.5 参数设置

8.6 G1回收器的常见操作步骤

G1的设计原则就是简化JVM性能调优,开发人员只需要简单的三步即可完成调优:

G1中提供了三种垃圾回收模式: YoungGC、 Mixed GC和Full GC, 在不同的条件下被触发。

8.7 适用场景

8.8 region分区

  使用G1收集器时,它将整个Java堆划分成约2048个大小相同的独立Region块,每个Region块大小根据堆空间的实际大小而定,整体被控制在1MB到32MB之间,且为2的N次幂,即1MB, 2MB, 4MB, 8MB, 1 6MB, 32MB。可以通过 -XX:G1HeapRegionSize设定。所有的Region大小相同,且在JVM生命周期内不会被改变。
  虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region (不需要连续)的集合。通过Region的动态分配方式实现逻辑上的连续。


8.9 G1回收器垃圾回收过程

G1 GC的垃圾回收过程主要包括如下三个环节:

8.10 记忆集与写屏障

8.11 G1回收过程详解

1 年轻代GC

2 并发标记过程

3 混合回收

当越来越多的对象晋升到老年代oldregion时,为了避免堆内存被耗尽,虚拟机会触发一个混合的垃圾收集器,即Mixed GC, 该算法并不是一个OldGC,除了回收整个Young Region,还会回收一部分的0ldRegion。这里需要注意:是一部分老年代, 而不是全部老年代。可以选择哪些oldRegion进行收集,从而可以对垃圾回收的耗时时间进行控制。也要注意的是Mixed GC并不是Full GC。

4 Full GC

  G1的初衷就是要避免Full GC的出现。但是如果上述方式不能正常工作,G1会停止应用程序的执行(Stop-The-World),使用单线程的内存回收算法进行垃圾回收,性能会非常差,应用程序停顿时间会很长。
  要避免Full GC的发生,一旦发生需要进行调整。什么时候会发生Full GC呢?比如堆内存太小,当G1在复制存活对象的时候没有空的内存分段可用,则会回退到full gc, 这种情况可以通过增大内存解决。

导致G1Full GC的原因可能有两个:

补充

  从Oracle官方透露出来的信息可获知,回收阶段(Evacuation)其实.本也有想过设计成与用户程序一起并发执行,但这件事情做起来比较复杂,考虑到G1只是回收一部分Region, 停顿时间是用户可控制的,所以并不迫切去实现,而选择把这个特性放到了G1之后出现的低延迟垃圾收集器(即ZGC)中。另外,还考虑到G1不是仅仅面向低延迟,停顿用户线程能够最大幅度提高垃圾收集效率,为了保证吞吐量所以才选择了完全暂停用户线程的实现方案。

8.12 优化建议

9 垃圾回收器总结


截止JDK 1.8,一共有7款不同的垃圾收集器。每一款不同的垃圾收集器都有不同的特点,在具体使用的时候,需要根据具体的情况选用不同的垃圾收集器。


不同厂商、不同版本的虚拟机实现差别很大。HotSpot 虚拟机在JDK7/8后所有收集器及组合(连线),如下图:
[图片上传中...(image-4da874-1596784089014-19)]

GC发展阶段: Serial => Parallel (并行) => CMS (并发) => G1 => ZGC

怎么选择垃圾回收器

10 GC日志分析


通过阅读GC日志,我们可以了解Java虛拟机内存分配与回收策略。内存分配与垃圾回收的参数列表

+PrintGC
[GC (Allocation Failure) 80832K一>19298K(227840K),0.0084018 secs]
[GC (Metadata GC Threshold) 109499K一>21465K (228352K),0.0184066 secs]
[Full GC (Metadata GC Threshold) 21 465K一>16716K (201728K),0.0619261 secs ]
GC、Full GC: GC的类型,GC只在新生代上进行,Full GC包括永生代,新生代, 老年代。
Allocation Failure: GC发生的原因。
80832K一> 19298K:堆在GC前的大小和GC后的大小。
228840k:现在的堆大小。
0.0084018 secs: GC持续的时间。
PrintGCDetails
[GC (Allocation Failure) [ PSYoungGen: 70640K一> 10116K(141312K) ] 80541K一>20017K (227328K),0.0172573 secs] [Times: user=0.03 sys=0.00, real=0.02 secs ]
[GC (Metadata GC Threshold) [PSYoungGen:98859K一>8154K(142336K) ] 108760K一>21261K (228352K),
0.0151573 secs] [Times: user=0.00 sys=0.01, real=0.02 secs]
[Full GC (Metadata GC Threshold) [PSYoungGen: 8154K一>0K(142336K) ] [ParOldGen: 13107K一>16809K(62464K) ] 21261K一>16809K (204800K),[Metaspace: 20599K一>20599K (1067008K) ],0.0639732 secs]
[Times: user=0.14 sys=0.00, real=0.06 secs]
GC,Full FC:同样是GC的类型
Allocation Failure: GC原因
PSYoungGen:使用了Parallel Scavenge并行垃圾收集器的新生代GC前后大小的变化
ParOldGen:使用了Parallel Old并行垃圾收集器的老年代GC前后大小的变化
Metaspace: 元数据区GC前后大小的变化,JDK1.8中引入了 元数据区以替代永久代
xxx secs : 指Gc花费的时间
Times: user: 指的是垃圾收集器花费的所有CPU时间,sys: 花费在等待系统调用或系统事件的时间, real :GC从开始到结束的时间,包括其他进程占用时间片的实际时间。

PrintGCTimeStamps

2019一09一24T22:15:24.518+0800:3.287: [GC(Allocation Failure) [ PSYoungGen: 1361 62K一>5113K(136192K) ] 141425K一>17632K (222208K) ,0.0248249 secs] [Times: user=0.05sys=0.00, real=0.03 secs ]
2019一09一24T22:15:25.559+0800:4.329: [ GC(Metadata GC Threshold)[PSYoungGen:97578K一>10068K(274944K) ] 110096K一>22658K (360960K),0.0094071 secs]
[Times: user=0\. 00sys=0.00, real=0\. 01 secs]
2019一09一24T22:15:25.569+0800:4.338: [Full GC (Metadata GC Threshold)[ PSYoungGen:10068K一>0K(274944K) ] [ ParoldGen: 12590K一>13564K (56320K) ] 22658K一>13564K (331264K) ,
[Metaspace: 20590K一>20590K(1067008K)], 0\. 0494875 secs]
[Times: user=0.17 sys=0\. 02,real=0.05 secs ]     

说明:带上了日期和时间

补充说明
堆空间日志分析 Minor GC Minor GC日志分析 Full GC Full GC日志分析
/**
 * 在jdk7 和 jdk8中分别执行
 * -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:SurvivorRatio=8 -XX:+UseSerialGC
 */
public class GCLogTest1 {
    private static final int _1MB = 1024 * 1024;

    public static void testAllocation() {
        byte[] allocation1, allocation2, allocation3, allocation4;
        allocation1 = new byte[2 * _1MB];
        allocation2 = new byte[2 * _1MB];
        allocation3 = new byte[2 * _1MB];
        allocation4 = new byte[4 * _1MB];
    }

    public static void main(String[] agrs) {
        testAllocation();
    }
}

GC前
GC后
日志分析工具使用

可以用一些工具去分析这些gc日志。
常用的日志分析工具有: GCViewer、GCEasy、GCHisto、GCLogViewer 、Hpjmeter、garbagecat等。

11 垃圾回收器的新发展

  GC仍然处于飞速发展之中,目前的默认选项G1 GC在不断的进行改进,很多我们原来认为的缺点,例如串行的Full GC、Card Table扫描的低效等,都已经被大幅改进,例如,JDK 10以后,Fu1l GC已经是并行运行,在很多场景下,其表现还略优于Parallel GC的并行Full GC实现。
  即使是Serial GC,虽然比较古老,但是简单的设计和实现未必就是过时的,它本身的开销,不管是GC相关数据结构的开销,还是线程的开销,都是非常小的,所以随着云计算的兴起,在Serverless等新的应用场景下,Serial GC找到了新的舞台。
  比较不幸的是CMS GC, 因为其算法的理论缺陷等原因,虽然现在还有非常大的用户群体,但在JDK9中已经被标记为废弃,并在JDK14版本中移除。

JDK11 新特性

Open JDK12 的Shenandoah GC:低停顿时间的GC (实验性)

革命性的ZGC

  ZGC官网链接与Shenandoah目标高度相似,在尽可能对吞吐量影响不大的前提下,实现在任意堆内存大小下都可以把垃圾收集的停顿时间限制在十毫秒以内的低延迟。
  《深入理解Java虚拟机》一书中这样定义ZGC: ZGC收集器是一款基于Region内存布局的,(暂时) 不设分代的,使用了读屏障、染色指针和内存多重映射等技术来实现可并发的标记-压缩算法的,以低延迟为首要目标的一款垃圾收集器。
  ZGC的工作过程可以分为4个阶段:并发标记一并发预备重分配一并发重分配一并发重映射等。
  ZGC几乎在所有地方并发执行的,除了初始标记的是STW的。所以停顿时间几乎就耗费在初始标记上,这部分的实际时间是非常少的。

测试数据如图:

在ZGC的强项停顿时间测试上,它毫不留情的将Parallel、G1拉开了两个数量级的差距。无论平均停顿、958停顿、998停顿、99. 98停顿,还是最大停顿时间,ZGC 都能毫不费劲控制在10毫秒以内。

JDK14新特性

其他垃圾回收器:AliGC

AliGC是阿里巴巴JVM团队基于G1算法,面 向大堆(LargeHeap)应用场景。指定场景下的对比:


当然,其他厂商也提供了各种独具一格的GC实现,例如比较有名的低延迟GC,Zing
https://www.infoq.com/articles/azul_gc_in_detail/

上一篇下一篇

猜你喜欢

热点阅读