Interview preparation -- JVM性能优化-垃圾收集器

评价GC策略指标
  • 以下指标评价GC处理器优缺点
    • 吞吐量:系统吞吐量. 业务线程耗时/系统总时间
    • 垃圾回收器负载:GC线程总耗时 / 业务线程总耗时 的比值
    • 停顿时间:总的stop the word 时间
    • 回收效率:GC在线程在完成垃圾对象标记后,多久能清理完
    • 反应时间:指当一个对象确认为垃圾后,多长时间内,他所能被清理
    • 堆内存分配:不同垃圾收集器内存分配不同,好的收集器应该有一个合理的分配方式。
新生代串行收集器(Serial)
  • JDK中最老的也是最基本的回收器,两个特点
    • 使用单线程进行垃圾回收
    • 第二他是独占式回收
  • 此垃圾回收器工作必然导致stop the world,但是他说一个最成熟的且高性能回收器,适用于新生代使用复制算法逻辑简单
  • JVM参数设置 -XX:+UseSerialGC来指定新生代中,老年代中使用串行收集器,在堆内存空间较小时候更有优势,速度快毫秒级
    在这里插入图片描述
老年代串行收集器(Serial Old)
  • 老年代不同在于使用标记压缩算法,同样是单线程独占式,因此同样需要stop the word,但是可以和多种新生代回收器配合使用,如JVM参数
    • -XX:UserSerialGC:新生代,老年代都用串行
    • -XX:UserParNewGC:新生代用并行收集器,老年代使用串行收集器
    • -XX:UseParallelGC:新生代用并行回收收集器,老年的用串行回收器
并行收集器(ParNew)
  • 只是简单的将串行收集器多线程化,他的回收策略,算法,参数都和串行回收器一样,也是独占说,也需要stop the word
  • 但是并行收集器多线程回收在多CPU情况下,停顿时间更短,在单个CPU下比串行回收更弱因为多线程有多余的上下文切换。
  • 用以下参数开启并行回收器:
    • -XX:+UserParNewGC:新生代使用并行收集器,老年代用串行收集器
    • -XX:+UserConcMarkSweepGC:新生代使用并行收集器,老年代使用CMS收集器
  • 并行收集器线程数可设置,一半是CPU数,当CPU数超过8 ,则用3+[(5*cpu)/8]

在这里插入图片描述

新生代并行回收(Parallel Scavenge)收集器
  • 同样用复制算法,他和并行收集器一样,都是多线程,独占式收集器,并且关注点在吞吐量:
    • 用以下参数开启:
    • -XX:+UseParallelGC :新生代使用并行回收收集器,老年代使用串行回收收集器
    • -XX:+UseParallelOldGC:新生代和老年代都使用并行回收处理器
  • 关键点:
    • 使用 -XX:MaxGCPauseMillis 设置最大GC停顿时间,GC会自动调节堆大小达到这个目的,例如设置很小,则必然让堆内存减少才能减少GC时间,这时候GC频次增加,吞吐量下降
    • 使用 -XX:GCTimeRatil 设置吞吐量,例如n,则系统话费不超过 1/(1+n)默认是99 也就是1%
    • 使用 -XX:+UseAdaptiveSizePolicy 设置自适应GC,这种模式下类似全自动,新生代,eden和survivor比例,晋升老年代堆年龄等参数会自动调整,以达到,堆大小和吞吐量的平衡点,只要设置JVM的最大堆代奥,和这三个参数,其他的都会自动完成调优
老年代并行回收收集器(Parallel Old)
  • 同样是一种多线程的收集器,但是用的是标记压缩算法,同样关注点在吞吐量,JDK1.6 中才引入
  • 使用-XX:UseParallelOldGC 可以在新生代和老年代都使用并行回收收集器
  • 使用-XX:parallelGCThreads 设置线程数量

在这里插入图片描述

CMS收集器
  • CMS(Concurrent Mark Swap)并发标记清除,使用标记清除算法,同时也使用多线程进行垃圾回收

在这里插入图片描述

  • 步骤如:
    • 初始标记:首先用单线程对堆中对象进行快速标记(独占式)
    • 并发标记:与业务线程一起进行对象标记
    • 重新标记:为修正上一阶段业务线程新产生的垃圾,重新在标记一次(独占式)
    • 并发清除:与业务线程并行对已标记的对象进行清除
    • 并发重置:此处是重置CMS的数据结构,准备下一次GC
  • CMS默认线程数是(ParallelGCThreads+3)/4,ParallelGCThreads是新生代并行收集器的线程数,同时也可以手动手指CMS线程,合理设置用来均衡在CPU资源紧张的时候减少对业务线程的影响
  • 通过使用-XX:CMSInitiatingOccupancyFraction来指定,默认68,也就是当老年代空间使用率到68%就开始GC,这样设置目的在于:需要保证留有足够空间在GC过程中也能同时运行业务线程
  • 空间碎片问题:可以通过-XX:+UseCMSCompactAtFullCollection开关让CMS在GC后进行一次碎片整理,同样-XX:CMSFullGCsBeforeCompaction参数可以用于设置多次GC后在进行一次内存压缩,独占式的整理
  • -XX:+UserConcMarkSweepGC:新生代使用并行收集器,老年代使用CMS收集器
  • CMS主要目的在于降低停顿时间
G1收集器
G1内存结构 Region
  • G1 的内存结构和传统内存空间划不同,G1 将内存分为默认 1M ~ 32MK的多个Region,逻辑上连续,物理上不连续,每个Region被标记为E,S,O,H,分别是Eden,Survivor,Old,Humongous。E,S年轻代,O,H,老年代

在这里插入图片描述

G1的GC模式
Young GC
  • Young GC回收的是所有年轻代的Region,当E区不能在分配新的对象时候会触。
  • 使用复制算法,E区域移动到S区,S区域空间不够直接到O区域
  • 同时S区域数据移动到新的S区域
  • 如果S区域部分对象到一定的年龄,直接到O区域

在这里插入图片描述

Mixed GC
  • Mixed GC叫混合GC,他回收所有年轻代 + 部分老年代

  • 什么时候触发 + 为什么是部分老年代?

  • 回收老年代参数是 -XX:MaxGCPauseMillis 用来制定一个G1 收集过程的目标停顿时间默认 200ms,G1 有一个停顿预测模型,他会有选择的挑选部分Region来做GC,以此来满足停顿时间的要求

  • Mixed GC 触发节点也是参数控制:XX:InitiatingHeapOccupancyPercent 表示老年代占整个堆大小的百分比,默认45%,达到这个阀值 就触发一次Mixed GC

  • Mixec GC分为2个阶段:

  • 第一个阶段:全局并发标记,可以细分为以下几个步骤

    • 初始标记:独占式,STW,从GC Root开始找到可访问到的堆。完成初始标记阶段
    • 并发标记:这个阶段从GC Root开始堆堆中对象标记, 标记线程与应用线程并行执行,收集各个Region存活对象信息
    • 最终标记:独占式,STW,标记那些在并发标记阶段发生变化的对象,将被回收
    • 清除垃圾:部分STW,这个阶段如果发现完全没有存活对象的Region,会将其整体会收到Region列表中,清除空的Region
  • 第二个阶段:拷贝存活对象

    • 全暂停阶段STW,负责将一部分Region里面存活的对象拷贝到空的Region里面(并行拷贝),然后回收原来的Region空间,本阶段可以自由选择任意多个Region来独立收集,被选择的Region构成一个集合,collection Set,集合中的Region的选定由停顿预测模型来决定,该阶段并不会处理所有的Region,因为时间可能不允许,选择高收益的少量Region。
      在这里插入图片描述
Full GC
  • G1 的垃圾回收过程是和应用程序并发执行的。当Mixed GC的速度,赶不上应用程序申请内存的速度时候,Mixed G1 会降级到Full GC,使用的是Serial GC(新生代,老年代都用串行收集)这种Full GC是单线程独占式的,会导致长时间的STW
  • 导致G1 Full GC的原因可能有以下两种
    • 拷贝存活对象时候,没有足够的to-space区域(可能是S或者O或者H)来存放晋升的对象
    • 并发处理过程完成之前堆内存被耗尽
SATB & incremental Update
  • 问题由来,在并发标记阶段,GC线程与 应用线程同时对对象进行修改,此时可能出现漏标记对象的情况。
  • SATB 全称 Snapshot-At-The-Begining,由字面意思就是GC开始时候存活的对象快照
    • 根据三色标记法,白色:没标记,灰色:标记了自身,但是子引用没标记,黑色:自身以及子引用都已标记
    • 当并发标记阶段有如下问题出现,D引用由A转到C,那么以后都无法被扫到而被删,CMS与GC解决方式如下

请添加图片描述

  • CMS中使用Incremental Update 关注的是引用的增加,此时C引用增加了,所以CMS 将C对象标记为灰色,下次重新扫描属性,这个地方其实是需要重新扫描灰色节点,这里会有性能上的问题,也会搜索其他的节点。
  • G1 使用SATB,在删除A— > D 的引用时候,此时会在GC引用栈中添加一个D,在再次标记的时候会重新扫面引用栈中的对象以此来重新标记,此处一不会修改父节点的状态,二不会引起灰色节点的重新便利,所以比CMS的更高效
G1 的RSet,CSet
  • 是用来辅助GC过程的一个数据结构,空间换时间的做法
  • RSet全称Remembered Set,和Card Table类似,Rset记录了其他Region中对象到本Region中对象的关系
  • CSet全称Collection Set 记录了GC要收集的Region的集合,并且可以是任意年代
  • 这样对于old --> young 和old --> old 的引用就不用扫描整个old区域,直接看Rse,Cset这个两个集合中数据即可
  • 例如标记的时候,只需遍历Cset找到Region,让后通过Rset找到引用关系即可,不需要便利整个堆提高效率
    在这里插入图片描述
G1 与 CMS对比
  • CMS

    • 优点:并发收集,低停顿
    • 缺点:
    1. CMS收集器对CPU资源非常敏感
    2. CMS收集器无法处理浮动垃圾(并发清除的时候产生的垃圾)
    3. CMS收集器是基于标记清除算法,会有内存碎片(虽然可以通过参数设置定时清理,但是STW)
    4. 停顿时间不可控
  • G1

    • 优点:
    1. 并行与并发,利用多CPU缩短STW时间
    2. 分代收集
    3. 空间整合:标记整理算法,没有碎片
    4. 可以预测停顿时间
    5. G1不再只正对年轻代或者老年代,整改堆都用同一个处理
  • 详细对比表

特征G1CMS
并发和分代
最大化释放堆内存
低延时
吞吐量
压实(碎片)
可预测性
新生代和老年代的物理隔离
什么时候用G1
  • 50%以上的堆被存活对象占用
  • 对象分配和晋升的速度变化非常大
  • 垃圾回收时间比较长
ZGC
  • JDK11新引入的ZGC收集器,不管是物理上还是逻辑上,ZGC中已经不存在新老年代的概念了,会分为一个个page,当进行GC操作时对page进行压缩,因此没有碎片问题
  • ZGC只能在64位操作系统上使用
  • ZGC支持TB级别内存
  • 堆内存变大后同样能做大停顿时间在10ms以内
案例分析
//线上案例. zhenai-advertising-api
#!/bin/bash
/usr/bin/java -cp /data/dubbo/zhenai-advertising-api/classes:/data/dubbo/zhenai-advertising-api/libs-zhenai/*:/data/dubbo/zhenai-advertising-api/libs/* 
-Xms2329m //最小堆 2.27G
-Xmx2329m //最大堆 2.27G
-Xss512k  //线程栈大小512k            
-XX:MetaspaceSize=256m 		//方法区初始值 
-XX:MaxMetaspaceSize=256m   //方法区最大值
-XX:+UnlockExperimentalVMOptions 	//docker配置
-XX:+UseCGroupMemoryLimitForHeap 	//docker配置
-Xmn1164m 			//新生代大小
-XX:+UseConcMarkSweepGC  // 新生代使用并行收集器,老年代使用CMS收集器
-XX:+CMSParallelRemarkEnabled    //采用并行标记方式降低停顿(默认开启)。               
-XX:+UseCMSInitiatingOccupancyOnly //让下一个老年代比例配置生效
-XX:CMSInitiatingOccupancyFraction=70   //代表老年代堆空间的使用率,默认值为68,此处70表示达到70%使用率开始GC
  • 如上配置中 使用的是PreNew+CMS 配合的垃圾收集算法,堆内存最大最小分别配置的-Xms =2329m -Xmx=2329,都是2.3G,因此他的年轻代的大小占用大概是 2329*30% = 700MB

  • 项目特点,请求量大每天1.5亿,但是主要用来做数据收集,数据归因等操作,逻辑不难,并且不需要非常高的响应,强调的是吞吐量

  • 在项目初期,发现在并发高峰期会有存在MQ消息堆积的问题,导致数据回传无法完成归因逻辑,从而导致数据匹配的不准确,因此开始研究了JVM的参数配置问题

    • 首先查看了一下GC日志,发现并发高峰时候会有频繁的PreNew的MinoGC并且每次GC后剩下1%都不到,并且MinoGC的频率比较大,因此我们增加了堆内存的大小,扩容到了5G,并且将新生代老年代的比例设置为 1:1
    • 接着调整了GC算法,之前用PreNew虽然能并发的利用多CPU,但是显然对于频繁的MinoGC下每次都需要STW,实际的吞吐量是降低的,因此我们将PreNew+CMS的组合替换成了G1 ,虽然在这种情况下一般都不会用到MixGC体现不出优势,但是相对于PreNew 而言G1 的YoungGC能利用并行回收的优势提升吞吐量,并且G1的停顿预测模型能够对YoungGC的停顿时间起到一个优化的作用。
    • 接着协调运维那边接入了对项的监控和报警,以保证程序的稳定性。
  • 之前使用PreNew+CMS的原因: 因为项目的业务逻辑简单,因此在运行中很少有对象能进入老年代中,所以在堆内存中基本上都是存活在年轻的的对象,推测是无法发挥出G1的优势。因此之前的想法是将堆内存减少,同时利用PreNew的并行收集来尽量缩减MinoGC的时间。但是实际情况发现频繁的MinoGC反而导致吞吐量下降了

  • 优化后的GC日志

//线上案例 zhenai-mobile-api
#!/bin/bash
/usr/bin/java -cp /data/dubbo/zhenai-mobile-api/classes:/data/dubbo/zhenai-advertising-api/libs-zhenai/*:/data/dubbo/zhenai-advertising-api/libs/* 
-Xms5734m 	// 最小堆内存5G+
-Xmx5734m 	//	最大堆内存5G+
-Xss512k    	// 线程栈,一个线程分配512k           
-XX:MetaspaceSize=512m 	//元数据区最小512
-XX:MaxMetaspaceSize=512m   // 元数据区最大 512   
-XX:+UnlockExperimentalVMOptions 
-XX:+UseCGroupMemoryLimitForHeap 
-XX:+UseG1GC 			// 使用G1 垃圾收集器
-XX:MaxGCPauseMillis=100 // 设置GC暂停等待时间,单位为毫秒
-XX:+ParallelRefProcEnabled                   
垃圾收集器常用组合

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值