• Java虚拟机(三):垃圾收集器 时间


    概述

      如果说收集算法是内存回收的方法论,那垃圾收集器就是内存回收的实践者。《Java虚拟机规范》中对垃圾收集器应该如何实现并没有做出任何规定,因此不同的厂商、不同版本的虚拟机所包含的垃圾收集器都可能会有很大差别,不同的虚拟机一般也都会提供各种参数供用户根据自己的应用特点和 要求组合出各个内存分代所使用的收集器。各款经典收集器之间的关系如下图所示:

      图中展示了七中作用于不同分代的收集器,如果两个收集器之间存在连线,就说明它们可以搭配使用。图中收集器所处的区域,则表示它是属于新生代收集器抑或老年代收集器。在介绍这些收集器各自的特性之前,让我们先来明确一个观点:虽然我们会对各个收集器进行比较,但并非为了挑选一个最好的收集器出来,虽然垃圾收集器的技术在不断进步,但直到现在还没有最好的收集器出现,更加不存在“万能”的收集器,所以我们选择的只是对具体应用最合适的收集器。分代收集器特点:

    • Java 堆内存被划分为新生代和老年代两部分,
    • 新生代主要使用标记-复制和标记-清除垃圾回收算法;老年代主要使用标记-整理垃圾回收算法。

    并行(Parallel)和并发(Concurrent)在垃圾收集器中可以理解为:

    • 并行(Parallel):并行描述的是多条垃圾收集器之间的关系,说明同一时间有多条这样的线程在协同工作,通常默认此时用户线程是出于等待状态。
    • 并发(Concurrent):并发描述的是垃圾收集器线程与用户线程之间的关系,说明同一时间垃圾收集器与用户线程都在运行。由于用户线程并未被冻结,所以程序仍然能响应服务请求,但由于垃圾收集器线程占用了一部分系统资源,此时应用程序的处理的吞吐量将受到一定影响。

    一、串行收集器

    1. Serial 垃圾收集器(单线程、复制算法)

      Serial收集器是最基础、历史最悠久的收集器,使用复制算法,曾经(JDK1.3.1 之前)是HotSpot虚拟机新生代收集器唯一的选择。

      Serial 是一个单线程工作的收集器,但它的“单线程”的意义并不仅仅是说明它只会使用一个处理器或一条收集线程去完成垃圾收集工作,更重要的是强调在它进行垃圾收集时,必须暂停其他所有工作线程,直到它收集结束。

      “Stop The World”这个词语也许听起来很酷,但这项工作是由虚拟机在后台自动发起和自动完成的,在用户不可知、不可控的情况下把用户的正常工作的线程全部停掉,这对很多应用来说都是不能接受的。从JDK1.3开始,一直到现在最新的JDK13,HotSpot虚拟机开发团队为消除或者降低用户线程因垃圾收集而导致停顿的努力一直持续进行着,从Serial收集器到Parallel收集器,再到Concurrent Mark Sweep(CMS)和Garbage First(G1)收集器,最终至现在垃圾收集器的最前沿成功Shenandoah和ZGC等,我们看到了一个个越来越构思精巧,越来越优秀,也越来越复杂的垃圾收集器不断涌现,用户线程的停顿时间在持续缩短,但是仍然没有办法彻底消除(这里不去讨论RTSJ中的收集器),探索更优秀垃圾收集器的工作仍在继续。

    • 事实上,迄今为止,Serial垃圾收集器依然是HotSpot虚拟机运行在客户端模式下的默认新生代垃圾收集器,它有着优于其他收集器的地方,那就是简单高效(与其他收集器的单线程相比),对于内存资源受限的环境,它是所有收集器里额外内存消耗(Memory Footprint)最小的;对于单核处理器或处理器核心数较少的环境来说,Serial收集由于没有线程交互的开销,专心做垃圾收集自然可以获得最高的单线程垃圾收集效率。
    • 在用户桌面的应用场景以及近年来流行的部分微服务应用中,分配给虚拟机管理的内存一般来说并不会特别大,收集几十兆甚至一两百兆的新生代(仅仅是指新生代使用的内存,桌面应用甚少超过这个容量),垃圾收集器的停顿时间完全可以控制在十几、几十毫秒,最多一百多毫秒以内,只要不是频繁发生收集,这点停顿时间对许多用户来说是完全可以接受的。所以,Serial收集器对于运行在客户端模式下的虚拟机来说是一个很好的选择。
    • 配置参数:-XX:+UseSerialGC

     2. Serial Old 收集器(单线程标记整理算法 )

    •  Serial  Old 是 Serial 垃圾收集器老年代版本,它同样是个单线程的收集器,使用标记-整理算法,
    • 这个收集器也主要是运行在 Client 默认的 java 虚拟机默认的老年代垃圾收集器。

    在 Server 模式下,主要有两个用途:
      1.   在 JDK1.5 之前版本中与新生代的 Parallel Scavenge 收集器搭配使用。
      2.   作为老年代中使用 CMS 收集器的后备垃圾收集方案。

    新生代 Serial 与老年代 Serial Old 搭配垃圾收集过程图:

    新生代 Parallel Scavenge 收集器与 ParNew 收集器工作原理类似,都是多线程的收集器,都使用的是复制算法,在垃圾收集过程中都需要暂停所有的工作线程。

    新生代 Parallel Scavenge/ParNew 与老年代 Serial Old 搭配垃圾收集过程图:

    二、并行收集器

    1. ParNew(Serial+多线程)

    • ParNew 垃圾收集器其实是 Serial 收集器的多线程版本,也使用复制算法,除了使用多线程进行垃圾收集之外,其余的行为和 Serial 收集器完全一样,ParNew 垃圾收集器在垃圾收集过程中同样也要暂停所有其他的工作线程
    • ParNew 虽然是除了支持多线程并行收集之外,其他与Serial 收集器相比并没有太多创新之处,但它却是不少运行在服务端模式下的HotSpot虚拟机,尤其是JDK 7之前的遗留系统中首选的新生代收集器,其中有一个与功能、性能无关但很重要的原因是:除了Serial收集器外,目前只有它能与CMS收集器配合工作。
    • 自JDK 9开始,ParNew加CMS收集器的组合就不再是官方推荐的服务端模式下的收集器解决方案了。官方希望它能完全被G1所取代,甚至还取消了ParNew加Serial Old以及Serial加CMS这两组收集器组合的支持(其实原本也很少人这样使用),并直接取消了-XX:+UseParNewGC参数,这意味着ParNew和CMS从此只能互相搭配使用,再也没有其他收集器能够和它们配合了。
    • ParNew 收集器默认开启和 CPU核心数量相同的线程数,可以通过-XX:ParallelGCThreads 参数来限制垃圾收集的线程数。
    • 配置参数:
      •   -XX:+UseParNewGC 开启
      •   -XX:ParallelGCThreads 限制线程数量

    2. Parallel Scavenge 收集器(多线程复制算法、高效)

    • Parallel  Scavenge 收集器也是一个新生代垃圾收集器,同样使用复制算法,也是能够并行收集的多线程收集器;
    • 它重点关注的是程序达到一个可控制的吞吐量(Thoughput,CPU 用于运行用户代码的时间/CPU 总消耗时间,即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)),
      高吞吐量可以最高效率地利用 CPU 时间,尽快地完成程序的运算任务,主要适用于在后台运算而不需要太多交互的任务。
    • 自适应调节策略也是 ParallelScavenge 收集器与 ParNew 收集器的一个重要区别。
    • 配置参数:
      • -XX:+UseParallelGC和-XX:+UseParallelOldGC,使用Parallel收集器+ 并行老年代
      • -XX:MaxGCPauseMillis参数可以控制最大垃圾收集的停顿时间
      • -XX:GCTimeRatio参数直接设置吞吐量大小,是一个大于0小于100的整数,也就是垃圾收集时间占总时间的比率,相当于吞吐量的倒数。譬如把此参数设置为19,那允许的最大垃圾收集时间就占总时间的5%(即1/(1+19)),默认值为99,即允许最大1%的垃圾收集时间。
      • -XX:+UseAdaptiveSizePolicy开启后,就不需要人工指定新生代的大小(-Xmn)、Eden与Survivor区的比例(-XX:SurvivorRatio)、晋升老年代对象大小(-XX:PretenureSizeThreshold)等细节参数了,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整这些参数以提供更合适的停顿时间或者最大吞吐量。这种调节方式成为垃圾收集的自适应的调节策略。

    3. Parallel Old 收集器(多线程标记整理算法)

    • Parallel Old 收集器是 Parallel Scavenge 的老年代版本,使用多线程的标记-整理算法,在 JDK1.6才开始提供。
    • 在 JDK1.6 之前,新生代使用 Parallel Scavenge 收集器只能搭配老年代的 Serial  Old 收集器,只能保证新生代的吞吐量优先,无法保证整体的吞吐量,Parallel Old 正是为了在老年代同样提供吞吐量优先的垃圾收集器,如果系统对吞吐量要求比较高,可以优先考虑新生代 Parallel  Scavenge和老年代 Parallel Old 收集器的搭配策略。

    新生代 Parallel Scavenge 和老年代 Parallel Old 收集器搭配运行过程图:

    三、并发收集器

    1. CMS收集器(多线程标记清除算法)

    • Concurrent  mark sweep(CMS)收集器是一种老年代垃圾收集器,其最主要目标是获取最短垃圾回收停顿时间,和其他老年代使用标记-整理算法不同,它使用多线程的标记-清除算法
      最短的垃圾收集停顿时间可以为交互比较高的程序提高用户体验。
    • 配置参数:-XX:+UseConcMarkSweepGC

    CMS工作机制相比其他的垃圾收集器更复杂,整个过程分为以下4个阶段:

    • 初始标记:只是标记一下 GC Roots 能直接关联的对象,速度很快,仍然需要暂停所有的工作线程。
    • 并发标记(和用户线程一起):主要标记过程,标记全部对象,和用户线程一起工作,不需要暂停工作线程。
    • 重新标记:由于并发标记时,用户线程依然运行,因此在正式清理前,再做修正,需要暂停所有的工作线程。
    • 并发清除(和用户线程一起):基于标记结果,直接清理对象,和用户线程一起工作,不需要暂停工作线程。

    特点:

    • 尽可能降低停顿
    • 会影响系统整体吞吐量和性能。比如,在用户线程运行过程中,分一半CPU去做GC,系统性能在GC阶段,反应速度就下降一半
    • 清理不彻底。因为在清理阶段,用户线程还在运行,会产生新的垃圾,无法清理
    • 因为和用户线程一起运行,不能在空间快满时再清理
      • -XX:CMSInitiatingOccupancyFraction设置触发GC的阈值
      • 如果不幸内存预留空间不够,就会引起concurrent mode failure
    • 使用串行收集器作为后备

    2. G1 收集器

      Garbage first(G1)收集器是垃圾收集器技术发展历史上的里程碑式的成果,它开创了收集器面向局部收集的设计思路和基于Region的内存布局形式。G1是一款主要面向服务端应用的垃圾收集器。是CMS的替代者和继承人,在JDK 9发布之日,G1宣告取代Parallel Scavenge加Parallel Old组合,成为服务端模式下的默认垃圾收集器,而CMS则沦落至被声明为不推荐使用的收集器。

      G1是一个面向全堆的收集器,不再需要其他新生代收集器的配合工作,相比与 CMS 收集器,G1 收集器两个最突出的改进是:

    1. 基于标记-整理算法,不产生内存碎片。
    2. 可以非常精确控制停顿时间,在不牺牲吞吐量前提下,实现低停顿垃圾回收。

     G1 收集器避免全区域垃圾收集,它把堆内存划分为大小固定的几个独立区域,并且跟踪这些区域的垃圾收集进度,同时在后台维护一个优先级列表,每次根据所允许的收集时间,优先回收垃圾最多的区域

      区域划分和优先级区域回收机制,确保 G1 收集器可以在有限时间获得最高的垃圾收集效率。

    四、常用参数

    -XX:+UseSerialGC:在新生代和老年代使用串行收集器
    -XX:SurvivorRatio:设置eden区大小和survivior区大小的比例
    -XX:NewRatio:新生代和老年代的比
    -XX:+UseParNewGC:在新生代使用并行收集器
    -XX:+UseParallelGC :新生代使用并行回收收集器
    -XX:+UseParallelOldGC:老年代使用并行回收收集器
    -XX:ParallelGCThreads:设置用于垃圾回收的线程数
    -XX:+UseConcMarkSweepGC:新生代使用并行收集器,老年代使用CMS+串行收集器
    -XX:ParallelCMSThreads:设定CMS的线程数量
    -XX:CMSInitiatingOccupancyFraction:设置CMS收集器在老年代空间被使用多少后触发
    -XX:+UseCMSCompactAtFullCollection:设置CMS收集器在完成垃圾收集后是否要进行一次内存碎片的整理
    -XX:CMSFullGCsBeforeCompaction:设定进行多少次CMS垃圾回收后,进行一次内存压缩
    -XX:+CMSClassUnloadingEnabled:允许对类元数据进行回收
    -XX:CMSInitiatingPermOccupancyFraction:当永久区占用率达到这一百分比时,启动CMS回收
    -XX:UseCMSInitiatingOccupancyOnly:表示只在到达阀值的时候,才进行CMS回收

  • 相关阅读:
    Atitit。D&D drag&drop拖拽功能c#.net java swing的对比与实现总结
    Atitit.js javascript异常处理机制与java异常的转换 多重catc hDWR 环境 .js exception process Vob7
    Atitit.web 视频播放器classid clsid 大总结quicktime,vlc 1. Classid的用处。用来指定播放器 1 2. 标签用于包含对象,比如图像、音
    ListView与Adapter的那些事儿
    (转)Android反面自动静音
    (转)socket 与 file_get_contents的区别和优势的简单介绍
    Android ArrayAdapter 详解
    ImageView相关
    Android dip,px,pt,sp 的区别
    (转)Android 程序获取、设置铃声、音量、静音、扬声器
  • 原文地址:https://www.cnblogs.com/wslook/p/9127679.html
Copyright © 2020-2023  润新知