热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

【JVM技术专题】深入分析CG管理和原理查缺补漏「番外篇」

前提概要本文主要针对HotspotVM中“CMSParNew”组合的一些使用场景进行总结。自Sun发布Java语言以来,开始使用GC技术来进行内存自动管理࿰




前提概要

本文主要针对 Hotspot VM 中“CMS + ParNew” 组合的一些使用场景进行总结。






自 Sun 发布 Java 语言以来,开始使用GC技术来进行内存自动管理,避免了手动管理带来的悬挂指针(Dangling Pointer)问题,很大程度上提升了开发效率,从此 GC 技术也一举成名。






GC 有着非常悠久的历史,1960 年有着“Lisp 之父”和“人工智能之父”之称的 John McCarthy 就在论文中发布了 GC 算法,60 年以来, GC 技术的发展也突飞猛进,但不管是多么前沿的收集器也都是基于三种基本算法的组合或应用,也就是说 GC 要解决的根本问题这么多年一直都没有变过。


RT 突然上涨,有 GC 耗时增大、线程 Block 增多、慢查询增多、CPU 负载高四个表象,到底哪个是诱因?如何判断 GC 有没有问题?使用 CMS 有哪些常见问题?如何判断根因是什么?如何解决或避免这些问题?




  • 建立知识体系: JVM的内存布局、对象内存结构,垃圾收集的算法和收集器特点,学习GC的基础知识。
  • 确定评价指标: 了解基本GC的评价方法,摸清如何设定独立系统的指标,以及在业务场景中判断 GC 是否存在问题的手段。
  • 场景调优实践: 运用掌握的知识和系统评价指标,分析与解决九种 CMS 中常见 GC 问题场景。
  • 总结优化经验:掌握一些常用的 GC 问题分析工具,有着相关的JVM的回收机制功能实现

GC 基础

基础概念

GC: GC本身有三种语义,下文需要根据具体场景带入不同的语义:


  • Garbage Collection :垃圾收集技术,名词。
  • Garbage Collector :垃圾收集器,名词。
  • Garbage Collecting :垃圾收集动作,动词。


Mutator: 生产垃圾的角色,也就是我们的应用程序,垃圾制造者,通过 Allocator 进行 allocatefree



TLAB:
Thread Local Allocation Buffer 的简写,基于CAS 的独享线程(Mutator Threads)可以优先将对象分配在 Eden 中的一块内存,因为是 Java 线程独享的内存区没有锁竞争,所以分配速度更快,每个 TLAB 都是一个线程独享的



Card Table: 中文翻译为卡表,主要是用来标记卡页的状态,每个卡表项对应一个卡页。当卡页中一个对象引用有写操作时,写屏障将会标记对象所在的卡表状态改为 dirty,卡表的本质是用来解决跨代引用的问题


JVM 内存划分

从JCP(Java Community Process)的官网中可以看到,目前 Java 版本最新已经到了 Java 16,未来的 Java 17 以及现在的 Java 11 和 Java 8 是 LTS 版本,JVM规范也在随着迭代在变更,由于本文主要讨论 CMS,此处还是放 Java 8 的内存结构。


GC 主要工作在 Heap 区和 MetaSpace 区(上图蓝色部分),在 Direct Memory 中,如果使用的是 DirectByteBuffer,那么在分配内存不够时则是 GC 通过 Cleaner#clean 间接管理(因为虽然内存分配和回收不归JVM直接管理,但是引用还存放在JVM的heap中可以间接通过引用清除内存对象)



任何自动内存管理系统都会面临的步骤:为新对象分配空间,然后收集垃圾对象空间,下面我们就展开介绍一下这些基础知识。


分配对象

Java中对象地址操作主要使用Unsafe调用了 C 的 allocate 和 free 两个方法,分配方法有两种:

  • 空闲链表(free list): 通过额外的存储记录空闲的地址,将随机 IO 变为顺序 IO,但带来了额外的空间消耗,此外还要增加O(1)级别的寻址时间
  • 碰撞指针(bump pointer): 通过一个指针作为分界点,需要分配内存时,仅需把指针往空闲的一端移动与对象大小相等的距离,分配效率较高,但使用场景有限。(必须规整的内存分配机制)



识别垃圾



引用计数法(Reference Counting): 对每个对象的引用进行计数,每当有一个地方引用它时计数器 +1、引用失效则 -1,引用的计数放到对象头中,大于 0 的对象被认为是存活对象。虽然循环引用的问题可通过 Recycler 算法解决,但是在多线程环境下,引用计数变更也要进行昂贵的同步操作,性能较低,早期的编程语言会采用此算法。




可达性分析,又称引用链法(Tracing GC): 从 GC Root 开始进行对象搜索,可以被搜索到的对象即为可达对象,此时还不足以判断对象是否存活/死亡,需要经过多次标记才能更加准确地确定,整个连通图之外的对象便可以作为垃圾被回收掉。目前 Java 中主流的虚拟机均采用此算法。





收集算法



自从有自动内存管理出现之时就有的一些收集算法,不同的收集器也是在不同场景下进行组合。



  • Mark-Sweep(标记-清除): 回收过程主要分为两个阶段,第一阶段为追踪(Tracing)阶段,即从 GC Root 开始遍历对象图,并标记(Mark)所遇到的每个对象,第二阶段为清除(Sweep)阶段,即回收器检查堆中每一个对象,并将所有未被标记的对象进行回收,整个过程不会发生对象移动。整个算法在不同的实现中会使用三色抽象(Tricolour Abstraction)、位图标记(BitMap)等技术来提高算法的效率,存活对象较多时较高效。



  • Mark-Compact (标记-整理): 这个算法的主要目的就是解决在非移动式回收器中都会存在的碎片化问题,也分为两个阶段,第一阶段与 Mark-Sweep 类似,第二阶段则会对存活对象按照整理顺序(Compaction Order)进行整理。主要实现有双指针(Two-Finger)回收算法、滑动回收(Lisp2)算法和引线整理(Threaded Compaction)算法等



  • Copying(复制): 将空间分为两个大小相同的 From 和 To 两个半区,同一时间只会使用其中一个,每次进行回收时将一个半区的存活对象通过复制的方式转移到另一个半区。有递归(Robert R. Fenichel 和 Jerome C. Yochelson 提出)和迭代(Cheney 提出)算法,以及解决了前两者递归栈、缓存行等问题的近似优先搜索算法。复制算法可以通过碰撞指针的方式进行快速地分配内存,但是也存在着空间利用率不高的缺点,另外就是存活对象比较大时复制的成本比较高。

把mark、sweep、compaction、copying这几种动作的耗时放在一起看,大致有这样的关系:

虽然 compaction 与 copying 都涉及移动对象,但取决于具体算法,compaction 可能要先计算一次对象的目标地址,然后修正指针,最后再移动对象。copying 则可以把这几件事情合为一体来做,所以可以快一些。另外,还需要留意 GC 带来的开销不能只看 Collector 的耗时,还得看 Allocator



如果能保证内存没碎片,分配就可以用 pointer bumping 方式,只需要挪一个指针就完成了分配,非常快。而如果内存有碎片就得用 freelist 之类的方式管理,分配速度通常会慢一些。



分代收集器


  • ParNew:一款多线程的收集器,采用复制算法,主要工作在 Young 区,可以通过-XX:ParallelGCThreads 参数来控制收集的线程数,整个过程都是STW的,常与CMS组合使用。
  • CMS: 以获取最短回收停顿时间为目标,采用“标记-清除”算法,分 4 大步进行垃圾收集,其中初始标记和重新标记会 STW ,多数应用于互联网站或者 B/S 系统的服务器端上,JDK9 被标记弃用,JDK14 被删除,详情可见 JEP 363。(性能和响应时间为优先)

分区收集器


  • G1:一种服务器端的垃圾收集器,应用在多处理器和大容量内存环境中,在实现高吞吐量的同时,尽可能地满足垃圾收集暂停时间的要求。
  • ZGC: JDK11 中推出的一款低延迟垃圾回收器,适用于大内存低延迟服务的内存管理和回收,SPECjbb 2015 基准测试,在 128G 的大堆下,最大停顿时间才 1.68 ms,停顿时间远胜于 G1 和 CMS。
  • Shenandoah: 由 Red Hat 的一个团队负责开发,与 G1 类似,基于 Region 设计的垃圾收集器,但不需要 Remember Set 或者 Card Table 来记录跨 Region 引用,停顿时间和堆的大小没有任何关系。停顿时间与 ZGC 接近

常用收集器

目前使用最多的是 CMS 和 G1 收集器,二者都有分代的概念,主要内存结构如下:


其他收集器



除此之外还有很多收集器,如 Metronome、Stopless、Staccato、Chicken、Clover 等实时回收器,Sapphire、Compressor、Pauseless 等并发复制/整理回收器,Doligez-Leroy-Conthier 等标记整理回收器。



GC的两个核心指标


  • 延迟(Latency): 也可以理解为最大停顿时间,即垃圾收集过程中一次 STW 的最长时间,越短越好,一定程度上可以接受频次的增大,GC 技术的主要发展方向。
  • 吞吐量(Throughput): 应用系统的生命周期内,由于 GC 线程会占用 Mutator 当前可用的 CPU 时钟周期,吞吐量即为 Mutator 有效花费的时间占系统总运行时间的百分比,例如系统运行了 100 min,GC 耗时 1 min,则系统吞吐量为 99%,吞吐量优先的收集器可以接受较长的停顿。



总结概括

目前各大互联网公司的系统基本都更追求低延时,避免一次 GC 停顿的时间过长对用户体验造成损失,衡量指标需要结合一下应用服务的 SLA,主要如下两点来判断:



简而言之,即为 一次停顿的时间不超过应用服务的 TP9999,GC 的吞吐量不小于 99.99% 。举个例子,假设某个服务 A 的 TP9999 为 80 ms,平均GC停顿为30ms,那么该服务的最大停顿时间最好不要超过 80 ms,GC 频次控制在 5 min 以上一次。




如果满足不了,那就需要调优或者通过更多资源来进行并联冗余。(大家可以先停下来,看看监控平台上面的 gc.meantime分钟级别指标,如果超过了 6 ms 那单机 GC 吞吐量就达不到4个9了。)




备注:除了这两个指标之外还有 Footprint(资源量大小测量)、反应速度等指标,互联网这种实时系统追求低延迟,而很多嵌入式系统则追求 Footprint。



重点需要关注的几个 GC Cause:


  • System.gc(): 手动触发 GC 操作。
  • CMS: CMS GC 在执行过程中的一些动作,重点关注 CMS Initial Mark 和 CMS Final Remark 两个 STW 阶段。
  • Promotion Failure: Old 区没有足够的空间分配给 Young 区晋升的对象(即使总可用内存足够大)。
  • Concurrent Mode Failure: CMS GC 运行期间,Old 区预留的空间不足以分配给新的对象,此时收集器会发生退化,严重影响 GC 性能,下面的一个案例即为这种场景。
  • GCLocker Initiated GC: 如果线程执行在 JNI 临界区时,刚好需要进行 GC,此时 GC Locker 将会阻止 GC 的发生,同时阻止其他线程进入 JNI 临界区,直到最后一个线程退出临界区时触发一次 GC。






推荐阅读
  • EPICS Archiver Appliance存储waveform记录的尝试及资源需求分析
    本文介绍了EPICS Archiver Appliance存储waveform记录的尝试过程,并分析了其所需的资源容量。通过解决错误提示和调整内存大小,成功存储了波形数据。然后,讨论了储存环逐束团信号的意义,以及通过记录多圈的束团信号进行参数分析的可能性。波形数据的存储需求巨大,每天需要近250G,一年需要90T。然而,储存环逐束团信号具有重要意义,可以揭示出每个束团的纵向振荡频率和模式。 ... [详细]
  • JVM 学习总结(三)——对象存活判定算法的两种实现
    本文介绍了垃圾收集器在回收堆内存前确定对象存活的两种算法:引用计数算法和可达性分析算法。引用计数算法通过计数器判定对象是否存活,虽然简单高效,但无法解决循环引用的问题;可达性分析算法通过判断对象是否可达来确定存活对象,是主流的Java虚拟机内存管理算法。 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • 纠正网上的错误:自定义一个类叫java.lang.System/String的方法
    本文纠正了网上关于自定义一个类叫java.lang.System/String的错误答案,并详细解释了为什么这种方法是错误的。作者指出,虽然双亲委托机制确实可以阻止自定义的System类被加载,但通过自定义一个特殊的类加载器,可以绕过双亲委托机制,达到自定义System类的目的。作者呼吁读者对网上的内容持怀疑态度,并带着问题来阅读文章。 ... [详细]
  • 2021最新总结网易/腾讯/CVTE/字节面经分享(附答案解析)
    本文分享作者在2021年面试网易、腾讯、CVTE和字节等大型互联网企业的经历和问题,包括稳定性设计、数据库优化、分布式锁的设计等内容。同时提供了大厂最新面试真题笔记,并附带答案解析。 ... [详细]
  • IvedonesearchingsimilarproblemsandIhaveavagueideaaboutwhatshouldIdo:tovectorizeev ... [详细]
  • 【go密码学】对称加密算法
    对称加密对称加密算法是相对于非对称加密算法而言,两者的区别在于,对称加密和加密和解密时使用相同的秘钥,而非对称加密在加密和解密时使用不同的秘钥(公钥和私钥)。常见的对称加密算法:D ... [详细]
  • 手机49kbps转换比特率256Kpbs{‘corpus_no’:‘7045177033217452815’,‘err_msg’:‘success.’,‘err_no’:0,‘re ... [详细]
  • 实战分析SpringBoot整合JSON,面试题附答案
    前言作为同时具备高性能、高可靠和高可扩 ... [详细]
  • 开发笔记:Java多线程深度探索
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了Java多线程深度探索相关的知识,希望对你有一定的参考价值。 ... [详细]
  • IntelliJ IDEA 卡成球了?
    在和同事的一次讨论中发现,对IntelliJIDEA内存采用不同的设置方案,会对IDE的速度和响应能力产生不同的影响。Don’tbeaScroogeandgiveyourIDEso ... [详细]
  • 微服务应用性能如何?APM监控工具来告诉你
    当微服务系统越来越庞大,各个服务间的调用关系也变得越来越复杂,需要一个工具来帮忙理清请求调用的服务链路。之前使用的是Sleuth+Zipkin的解决方案,最近发现应 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • Java 11相对于Java 8,OptaPlanner性能提升有多大?
    本文通过基准测试比较了Java 11和Java 8对OptaPlanner的性能提升。测试结果表明,在相同的硬件环境下,Java 11相对于Java 8在垃圾回收方面表现更好,从而提升了OptaPlanner的性能。 ... [详细]
  • 本文整理了Java面试中常见的问题及相关概念的解析,包括HashMap中为什么重写equals还要重写hashcode、map的分类和常见情况、final关键字的用法、Synchronized和lock的区别、volatile的介绍、Syncronized锁的作用、构造函数和构造函数重载的概念、方法覆盖和方法重载的区别、反射获取和设置对象私有字段的值的方法、通过反射创建对象的方式以及内部类的详解。 ... [详细]
author-avatar
手机用户2502931567
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有