持续创造,加快成长!这是我参加「日新计划 10 月更文应战」的第30天,点击查看活动详情

前言

废物搜集机制是 Java 的招牌才能,极大地提高了开发功率。现在,废物搜集几乎成为现代言语的标配,即使通过如此长时刻的开展, Java 的废物搜集机制依然在不断的演进中,不同大小的设备、不同特征的运用场景,对废物搜集提出了新的应战,这当然也是面试的热门。

本篇博文的重点是,Java 常见的废物搜集器有哪些?

概述

实践上,废物搜集器(GC,Garbage Collector)是和详细 JVM 完成严密相关的,不同厂商(IBM、Oracle),不同版别的 JVM,供给的挑选也不同。接下来,我来谈谈最干流的 Oracle JDK。

  • Serial GC,它是最古老的废物搜集器,“Serial” 体现在其搜集作业是单线程的,而且在进行废物搜集进程中,会进入臭名昭著的 “Stop-The-World” 状态。当然,其单线程规划也意味着精简的 GC 完成,无需保护杂乱的数据结构,初始化也简略,所以一直是 Client 形式下 JVM 的默许选项。从时代的角度,一般将其老时代完成独自称作 Serial Old,它选用了符号 – 收拾(Mark-Compact)算法,区别于新生代的仿制算法。Serial GC 的对应 JVM 参数是:

    -XX:+UseSerialGC
    
  • ParNew GC,很显着是个新生代 GC 完成,它实践是 Serial GC 的多线程版别,最常见的运用场景是合作老时代的 CMS GC 作业,下面是对应参数

    -XX:+UseConcMarkSweepGC -XX:+UseParNewGC
    
  • CMS(Concurrent Mark Sweep) GC,基于符号 – 铲除(Mark-Sweep)算法,规划方针是尽量减少中止时刻,这一点关于 Web 等反应时刻灵敏的运用非常重要,一直到今天,依然有许多体系运用 CMS GC。可是,CMS 选用的符号 – 铲除算法,存在着内存碎片化问题,所以难以防止在长时刻运转等情况下产生 full GC,导致恶劣的中止。别的,已然强调了并发(Concurrent),CMS 会占用更多 CPU 资源,并和用户线程争抢。

  • Parallel GC,在前期 JDK 8 等版别中,它是 server 形式 JVM 的默许 GC 挑选,也被称作是吞吐量优先的 GC。它的算法和 Serial GC 比较相似,尽管完成要杂乱的多,其特点是新生代和老时代 GC 都是并行进行的,在常见的服务器环境中愈加高效。开启选项是:

    -XX:+UseParallelGC
    

别的,Parallel GC 引入了开发者友爱的装备项,咱们能够直接设置暂停时刻或吞吐量等方针,JVM 会自动进行适应性调整,例如下面参数:

-XX:MaxGCPauseMillis=value
-XX:GCTimeRatio=N // GC时刻和用户时刻比例 = 1 / (N+1)
  • G1 GC 这是一种兼顾吞吐量和中止时刻的 GC 完成,是 Oracle JDK 9 以后的默许 GC 选项。G1 能够直观的设定中止时刻的方针,比较于 CMS GC,G1 未必能做到 CMS 在最好情况下的延时中止,可是最差情况要好许多。
    G1 GC 依然存在着时代的概念,可是其内存结构并不是简略的条带式区分,而是相似棋盘的一个个 region。Region 之间是仿制算法,但整体上实践可看作是符号 – 收拾(Mark-Compact)算法,能够有效地防止内存碎片,尤其是当 Java 堆非常大的时分,G1 的优势愈加显着。
    G1 吞吐量和中止体现都非常不错,而且依然在不断地完善,与此一起 CMS 现已在 JDK 9 中被符号为抛弃(deprecated),所以 G1 GC 值得你深化把握。

正文

废物搜集的原理和根底概念

榜首,自动废物搜集的前提是清楚哪些内存能够被释放。这一点能够结合我前面临 Java 类加载和内存结构的剖析,来考虑一下。

主要便是两个方面,最主要部分便是目标实例,都是存储在堆上的;还有便是办法区中的元数据等信息,例如类型不再运用,卸载该 Java 相好像是很合理的。

关于目标实例搜集,主要是两种根本算法,引证计数和可达性剖析。

  • 引证计数算法,望文生义,便是为目标增加一个引证计数,用于记载目标被引证的情况,假如计数为 0,即表示目标可收回。这是许多言语的资源收回挑选,例如因人工智能而愈加火热的 Python,它更是一起支撑引证计数和废物搜集机制。详细哪种最优是要看场景的,业界有大规模实践中仅保存引证计数机制,以提高吞吐量的测验。Java 并没有挑选引证计数,是由于其存在一个根本的难题,也便是很难处理循环引证联系。
  • 别的便是 Java 挑选的可达性剖析,Java 的各种引证联系,在某种程度上,将可达性问题还进一步杂乱化,详细请参考 【JAVA】强引证、软引证、弱引证、幻象引证有什么区别?,这种类型的废物搜集一般叫作追寻性废物搜集(Tracing Garbage Collection)。其原理简略来说,便是将目标及其引证联系看作一个图,选定活动的目标作为 GC Roots,然后盯梢引证链条,假如一个目标和 GC Roots 之间不可达,也便是不存在引证链条,那么即可以为是可收回目标。JVM 会把虚拟机栈和本地办法栈中正在引证的目标、静态属性引证的目标和常量,作为 GC Roots。

办法区无用元数据的收回比较杂乱,我简略梳理一下。还记得我对类加载器的分类吧,一般来说初始化类加载器加载的类型是不会进行类卸载(unload)的;而一般的类型的卸载,往往是要求相应自定义类加载器自身被收回,所以许多运用动态类型的场合,需求防止元数据区(或许前期的永久代)不会 OOM。在 8u40 以后的 JDK 中,下面参数现已是默许的:

-XX:+ClassUnloadingWithConcurrentMark

第二,常见的废物搜集算法,我以为整体上有个了解,了解相应的原理和优缺点,就现已足够了,其主要分为三类:

  • 仿制(Copying) 算法,我前面讲到的新生代 GC,根本都是基于仿制算法,进程就如 【JAVA】Java 常见的废物搜集器有哪些? 所介绍的,将活着的目标仿制到 to 区域,仿制进程中将目标次序放置,就能够防止内存碎片化。这么做的价值是,已然要进行仿制,既要提前预留内存空间,有一定的糟蹋;别的,关于 G1 这种分拆成为许多 region 的 GC,仿制而不是移动,意味着 GC 需求保护 region 之间目标引证联系,这个开支也不小,不管是内存占用或许时刻开支。
  • 符号 – 铲除(Mark-Sweep) 算法,首要进行符号作业,标识出一切要收回的目标,然后进行铲除。这么做除了符号、铲除进程功率有限,别的便是不可防止的呈现碎片化问题,这就导致其不适合特别大的堆;不然,一旦呈现 Full GC,暂停时刻或许根本无法承受。
  • 符号 – 收拾(Mark-Compact),相似于符号 – 铲除,但为防止内存碎片化,它会在整理进程中将目标移动,以确保移动后的目标占用连续的内存空间。留意,这些只是根本的算法思路,实践 GC 完成进程要杂乱的多,现在还在开展中的前沿 GC 都是复合算法,而且并行和并发兼备。

留意,这些只是根本的算法思路,实践 GC 完成进程要杂乱的多,现在还在开展中的前沿 GC 都是复合算法,而且并行和并发兼备。

废物搜集进程的了解

在 【JAVA】Java 常见的废物搜集器有哪些? 对堆结构进行了比较详细的区分,在废物搜集的进程,对应到 Eden、Survivor、Tenured 等区域会产生什么改变呢?

这实践上取决于详细的 GC 方法,先来熟悉一下一般的废物搜集流程,我画了一系列示意图,希望能有助于你了解清楚这个进程。

榜首,Java 运用不断创建目标,一般都是分配在 Eden 区域,当其空间占用到达一定阈值时,触发 minor GC。依然被引证的目标(绿色方块)存活下来,被仿制到 JVM 挑选的 Survivor 区域,而没有被引证的目标(黄色方块)则被收回。留意,我给存活目标符号了“数字 1”,这是为了标明目标的存活时刻。

【JAVA】Java 常见的垃圾收集器有哪些?

第二, 通过一次 Minor GC,Eden 就会闲暇下来,直到再次到达 Minor GC 触发条件,这时分,别的一个 Survivor 区域则会成为 to 区域,Eden 区域的存活目标和 From 区域目标,都会被仿制到 to 区域,而且存活的年纪计数会被加 1。

【JAVA】Java 常见的垃圾收集器有哪些?

第三, 相似第二步的进程会产生许多次,直到有目标年纪计数到达阈值,这时分就会产生所谓的提升(Promotion)进程,如下图所示,超越阈值的目标会被提升到老时代。这个阈值是能够通过参数指定:

-XX:MaxTenuringThreshold=<N>

【JAVA】Java 常见的垃圾收集器有哪些?

后面便是老时代 GC,详细取决于挑选的 GC 选项,对应不同的算法。下面是一个简略符号 – 收拾算法进程示意图,老时代中的无用目标被铲除后, GC 会将目标进行收拾,以防止内存碎片化。

【JAVA】Java 常见的垃圾收集器有哪些?

一般咱们把老时代 GC 叫作 Major GC,将对整个堆进行的整理叫作 Full GC,可是这个也没有那么绝对,由于不同的老时代 GC 算法其实体现差异很大,例如 CMS,“concurrent”就体现在整理作业是与作业线程一起并发运转的。

GC 的新开展

GC 依然处于飞速开展之中,现在的默许选项 G1 GC 在不断的进行改善,许多咱们本来以为的缺点,例如串行的 Full GC、Card Table 扫描的低效等,都现已被大幅改善,例如, JDK 10 以后,Full GC 现已是并行运转,在许多场景下,其体现还略优于 Parallel GC 的并行 Full GC 完成。

即使是 Serial GC,尽管比较古老,可是简略的规划和完成未必便是过时的,它自身的开支,不管是 GC 相关数据结构的开支,仍是线程的开支,都是非常小的,所以跟着云计算的兴起,在 Serverless 等新的运用场景下,Serial GC 找到了新的舞台。

比较不幸的是 CMS GC,由于其算法的理论缺点等原因,尽管现在还有非常大的用户集体,可是现已被符号为抛弃,假如没有安排自动承担 CMS 的保护,很有或许会在未来版别移除。

假如你有关注现在尚处于开发中的 JDK 11,你会发现,JDK 又增加了两种全新的 GC 方法,分别是:

  • Epsilon GC,简略说便是个不做废物搜集的 GC,好像有点古怪,有的情况下,例如在进行性能测验的时分,或许需求清晰判别 GC 自身产生了多大的开支,这便是其典型运用场景。
  • ZGC,这是 Oracle 开源出来的一个超级 GC 完成,具备令人惊奇的扩展才能,比如支撑 T bytes 等级的堆大小,而且保证绝大部分情况下,推迟都不会超越 10 ms。尽管现在还处于实验阶段,仅支撑 Linux 64 位的渠道,但其现已体现出的才能和潜力都非常令人等待。

当然,其他厂商也供给了各种独具一格的 GC 完成,例如比较有名的低推迟 GC,Zing 和 Shenandoah 等;

后记

以上便是【JAVA】Java 常见的废物搜集器有哪些?的一切内容了;

从整体上梳理了现在的干流 GC 完成,包含根本原理和算法,并结合我前面介绍过的内存结构,对简要的废物搜集进程进行了介绍,希望能够对你的相关实践有所帮助。

上篇精讲:【JAVA】JVM 内存区域的区分

我是,等待你的关注;

创造不易,请多多支撑;

系列专栏:面试精讲 JAVA