G1垃圾回收器在并发场景调优
一、序言
目前企业级主流使用的Java版本是8,垃圾回收器支持手动修改为G1,G1垃圾回收器
是Java 11的默认设置,因此G1垃圾回收器可以用很长时间,现阶段垃圾回收器优化意味着针对G1垃圾回收器优化。
为了简化讨论,下面假设针对4C/16G
物理机器进行优化。
二、G1概览
(一)了解G1
1、最大堆大小
G1管理的最大堆大小为64G。每个Region的大小通过-XX:G1HeapRegionSize
来设置,大小为1~32MB
,默认最多可以有2048个Region,G1能管理的最大堆内存是32MB*2048=64G
。
使用G1垃圾回收器最小堆内存应为1MB*2048=2GB
,低于此值建议使用其它垃圾回收器。
2、Region大小
Region大小为1~32MB
,具体取值有1MB、2MB、4MB、8MB、16MB、32MB,Region大小优化与大对象有关,当对象占用内存超过Region的一半时将被视为大对象。
被标记为大对象将不利于垃圾回收。
3、获取默认值
查看本地JVM特别是G1垃圾回收器当前的默认值。
java -XX:+PrintFlagsInitial >> ~/1.txt
(二)三种GC模式
G1垃圾回收器有两种垃圾回收模式,新生代回收
和混合回收
,特殊情况下会切换到Full GC
。
1、新生代回收
新生代回收在最大停顿时间内,会处理所有Eden区的垃圾。具体操作是将Eden区所有存活的对象复制到Survivor区,同时清空Eden区。
新生代回收伴随着应用暂停
,最长停顿时间不超过最大停顿时间
,新生代回收尽管有暂停机制,考虑到并行回收的特性,回收逻辑相对简单,回收效率依然较高。一般而言,新生代回收实际耗时通常低于最大停顿时间。
新生代回收触发时机是新创建的对象在Eden区找不到足够的存储空间。
2、混合回收
混合回收伴随着新生代回收和老年代回收,在最大停顿时间范围内,会处理大部分Eden区的垃圾和一部分老年代垃圾。
老年代回收毫无疑问会伴随着应用暂停。混合回收操作比较复杂,相对新生代回收来说,单位时间回收的垃圾数要少,回收效率要低。一般而言,混合回收的实际耗时通常接近或者等于最大停顿时间。
混合回收触发时机是由参数InitiatingHeapOccupancyPercent
控制,默认值为45,含义是老年代占用空间大小与堆的总大小比值超过此数便会触发混合回收。
默认值45%
是比较合理的,不建议所谓的调优。老年代回收策略同样是将选定Region区内存活的对象复制到空闲Region区,混合回收伴随着回收新生代垃圾能够清理出更大的空闲Region区来存放老年区存活对象,保证回收过程能够正常进行。
老年区存活对象一般较多,对象在内存中复制耗时较长,因此相对来说混合回收效率较低。
3、Full GC
Full GC是所有G1垃圾回收调优者尽力回避的情况,单线程回收垃圾,回收对象是整个堆,不再受最长停顿时间约束,一旦出现此情况,意味着应用的响应时间无情的变长。
当应用不定期进入Full GC
状态时,与其任由其单线程重塑堆内存,不如采用冗余策略,在流量低谷时刻,逐一重启应用,主动重塑堆内存空间。
流量高峰期出现Full GC现象及其应对策略后面再讨论。
(三)默认参数
1、堆内存
参数 | 默认值 | 说明 | 优化建议 |
---|---|---|---|
MaxGCPauseMillis | 200ms | 最大停顿时间 | |
G1HeapRegionSize | 不设置时启发式推断 | ||
G1NewSizePercent | 5 | 新生代最小百分比 | |
G1MaxNewSizePercent | 60 | 新生代最大百分比 |
2、新生代内存回收
参数 | 默认值 | 说明 | 优化建议 |
---|---|---|---|
ParallelGCThreads | 并行GC线程数,会根据CPU核数推断 | 默认值 | |
MaxTenuringThreshold | 15 | 从新生代晋升到老年代年龄阈值 | |
SurvivorRatio | 8 | Eden和一个Survivor的比例 | |
TargetSurvivorRatio | 50 | Survivor区内存使用率,增大该值会降低到老年代概率 | |
+G1EagerReclaimHumongousObjects | true | 是否在YGC时回收大对象 |
3、混合回收
参数 | 默认值 | 说明 | 优化建议 |
---|---|---|---|
G1MixedGCCountTarget | 8 | 值越大,收集老年代分区越少 | |
G1OldCSetRegionThresholdPercent | 10 | 表示一次最多收集10%的分区 |
三、垃圾在堆中流转
垃圾回收器调优的关键是尽可能减少Mixed GC的频率,换句话说尽可能减少垃圾流转到老年代
。GC调优便是认识垃圾在堆中的流转规律,从而对流向老年代的垃圾予以提前干涉,使之尽可能留在新生代
。
垃圾在新生代(主要指Eden区)中,垃圾回收使用YGC,回收线程与应用线程并发进行,垃圾回收对应用透明进行,假如CPU算力充足的话,应用几乎感觉不到垃圾在回收进行。
垃圾在老年代中,垃圾回收采用Mixed GC,回收线程开始工作时,应用线程阻塞,等待回收线程工作完毕有,应用线程重新被唤醒。频繁的Mixed GC对应用的吞吐量产生不良影响。
1、对象如何进入老年代
一般而言,新创建的对象会存在于新生代的Eden区,下一次垃圾回收处罚便直接回收了。如果对象比较顽强(继续被其它对象引用),那么会在Survivor区流转,每GC一次,仍然不能被垃圾回收,那么年龄加一,继续在S0和S1区流转,当年龄增长到一定的阈值,直接进入老年代。
(1)大对象直接到老年代
新创建的对象如果过大,那么不经过新生代,直接进入老年代。控制对象大小阈值有参数-XX:PretenureSizeThreshold
决定,单位字节
。
(2)动态年龄判断
除了对象在S0和S1区反复流转年龄变化外,垃圾回收维护另外一套独立的年龄判定规则:如果YGC后尚未被回收的垃圾超过了Survivor区的50%,那么超过的这批对象会直接进入老年代。
12G * 60% * 10% * 50% * 1024 = 737MB
动态年龄判定规则要求每次YGC尽可能的彻底,意味着每次GC的最长时间不能太短,默认200毫秒是比较合理的值。
如果预设置的最长停顿时间过短,那么每次GC后存活大量尚未被回收的垃圾,S区容量有限,不该进入老年代的垃圾快速在老年代堆积,频繁的Mixed GC不可避免。
2、高并发加速进入老年代
在高并发场景下,CPU和内存资源吃紧,负载很高,不确定的性能抖动加速垃圾进入老年代。
举例说明,DAO层查询数据库,一次完整的会话结束后,整个会话中产生的对象垃圾在Eden区应当被全部回收。由于网络波动,数据库处理能力的限制,大量会话超时。在此过程中这部分对象垃圾很可能在快速S0和S1流转中叠加年龄,或者触发动态年龄判定,直接进入老年代。
老年代内存空间不够用,触发Mixed GC,Mixed GC直接副作用是应用卡顿。
四、调优步骤
1、设置垃圾回收器
Java 8需要手动指定G1垃圾回收器,命令行添加-XX:+UseG1GC
参数。
2、设置堆大小
设置内存堆大小有两点需要注意:初始堆大小与最大堆大小保持一致;堆大小占物理内存大小75%~80%
,给系统核心服务预留必要的内存。
参数-Xmx12G
设置初始堆大小;参数-Xms12G
设置最大堆大小。
3、元空间设置
元空间是指存储静态类、静态方法、常量等特殊变量的内存区域。
参数-XX:MetaspaceSize=1G
设置元空间初始大小;参数-XX:MaxMetaspaceSize=1G
设置元空间最大大小。
4、GC停顿时间
GC停顿时间
是指每次YGC
或者Mixed GC
的最大时间,垃圾回收器会根据用户设置的期望时间动态选择垃圾扫描的范围,如果设置时间过小,可能总有一部分垃圾不能得到回收。单位毫秒
。
-XX:MaxGCPauseMillis=200
5、新生代大小
参数-XX:G1NewSizePercent
设置新生代初始大小,默认为5%
;参数-XX:G1MaxNewSizePercent
设置新生代最大大小,默认为60%
。
新生代内部细化为 Eden
区和两个 Survivor
,默认比例是: 8:1:1
Eden: 12G * 60%* 80% = 5.76G
S0: 12G * 60%* 10% = 0.72G
S1: 12G * 60%* 10% = 0.72G
假设并发系统每秒创建500MB的对象,假设每次YGC根据预先设置的最长停顿时间都能够扫描到Eden Region,那么此并发系统大约每隔10秒需要进行一次YGC。
五、调优实践
GC垃圾回收调优是在物理硬件受限制,并且有调优的理论空间下进行的。条件允许的话,直接升级硬件配置特别是物理内存配置,能够有效降低GC频率。比如8C32G
或者16C64G
等。
1、频繁的YGC
当并发量较大时,频繁的YGC时必然的,单位时间类创建了更多的对象,使用完毕之后成为了垃圾。频繁的YGC有加速S区对象流向老年代的可能,尽可能保证每次YGC的实际耗时低于预设置的最长垃圾回收时间(默认200毫秒),以便能够每次都能将新生代垃圾清理完成,尽可能延缓垃圾流向老年代。
2、频繁的Mixed GC
在G1垃圾回收器中,没有所谓的Mixed GC的概念,Mixed GC类似于F·GC,不同的是Mixed GC除了回收老年代,同时也回收新生代,共同之处在于都会产生STW
。
频繁的Mixed GC
本质是大量应该在新生代回收的垃圾进入了老年代,解决思路是排查哪些哪些垃圾(对象)应该留在新生代,却流转到老年代。
(1)大对象
检查应用程序是否周期性的创建大对象,大对象的阈值由参数-XX:PretenureSizeThreshold
控制。假如内存有优化空间的前提下适当调高此值,不得超过S区的一半(似乎没有这么大的对象),副作用是新生代存放对象数量相应变少,Eden区内存更快的用完,YGC相应的变频繁一些。
从业务的角度来讲,大对象产生必有其产生的原因,从这个角度优化可能性不高,垃圾回收器优化尽可能屏蔽业务层代码,毕竟对开发提要求让其不要创建大对象不现实。
(2)元空间
元空间耗尽也会引发Mixed GC
,考虑到元空间存储内容的特殊性,因元空间耗尽导致GC频率提高并没有很好的办法。单纯提高元空间大小会压缩新生代大小,新生代变小,对象流转到老年代的数量会变多,老年代内存消耗加快,同样会提高GC的频率。
因元空间耗尽引发的Mixed GC
,相对来说增加物理内存是比较优的解决方式。
3、Full GC
尽管Mixed GC
被触发时,应用会暂时停止响应(默认值是200毫秒),暂停的时间是相对可控的。
如果在进行Mixed GC
时,空闲的Region无法保存存活的对象,Mixed GC无法正常进行时,垃圾回收会切换到 G1 之外的 Serial Old GC
来收集整个堆,包括新生代、老年代、元空间等。
进入Serial Old GC
垃圾回收状态,垃圾回收不再受最长回收时间约束,采用单线程进行标记、清理和压缩整理,应用可能进入假死
状态。也许重启应用,重新分配堆内存,将堆内存彻底洗牌,也许会更好。
G1垃圾回收调优的关键是不要出现Full GC,因此对于敏感的参数千万不要乱调优,否则不仅达不到理想想过,反而更糟糕。
G1垃圾回收器在并发场景调优的更多相关文章
- 深入浅出具有划时代意义的G1垃圾回收器
G1诞生的背景 Garbage First(简称G1)收集器是垃圾收集器技术发展历史上的里程碑式的成果,它开创了收集器面向局部收集的设计思路和基于Region的内存布局形式.HotSpot开发团队最初 ...
- JVM学习——G1垃圾回收器(学习过程)
JVM学习--G1垃圾回收器 把这个跨时代的垃圾回收器的笔记独立出来. 新生代:适用复制算法 老年代:适用标记清除.标记整理算法 二娃本来看G1的时候觉得比较枯燥,但是后来总结完之后告诉我说,一定要慢 ...
- G1垃圾回收器
垃圾回收器的发展历程 背景 01.G1解决的问题 G1垃圾回收器是04年正式提出,12开始正式支持,在17年作为JDK9默认的垃圾处理器. 在04年的时候,java程序堆的内存越来越大,从而导致程序中 ...
- JVM垃圾回收机制总结:调优方法
转载: JVM垃圾回收机制总结:调优方法 JVM 优化经验总结 JVM 垃圾回收器工作原理及使用实例介绍
- G1 垃圾回收器简单调优
G1: Garbage First 低延迟.服务侧分代垃圾回收器. 详细介绍参见:JVM之G1收集器,这里不再赘述. 关于调优目标:延迟.吞吐量 一.延迟,单次的延迟 单次的延迟关系到服务的响应时延, ...
- JAVA之G1垃圾回收器
概述 G1 GC,全称Garbage-First Garbage Collector,通过-XX:+UseG1GC参数来启用,作为体验版随着JDK 6u14版本面世,在JDK 7u4版本发行时被正式推 ...
- 线上Redis高并发性能调优实践
项目背景 最近,做一个按优先级和时间先后排队的需求.用 Redis 的 sorted set 做排队队列. 主要使用的 Redis 命令有, zadd, zcount, zscore, zrange ...
- MySQL面试必考知识点:揭秘亿级高并发数据库调优与最佳实践法则
做业务,要懂基本的SQL语句: 做性能优化,要懂索引,懂引擎: 做分库分表,要懂主从,懂读写分离... 数据库的使用,是开发人员的基本功,对它掌握越清晰越深入,你能做的事情就越多. 今天我们用10分钟 ...
- [转]10分钟梳理MySQL知识点:揭秘亿级高并发数据库调优与最佳实践法则
转:https://mp.weixin.qq.com/s/RYIiHAHHStIMftQT6lQSgA 做业务,要懂基本的SQL语句: 做性能优化,要懂索引,懂引擎: 做分库分表,要懂主从,懂读写分离 ...
随机推荐
- 21个实用便利的PHP代码
转载请注明来源:https://www.cnblogs.com/hookjc/ 1. PHP可阅读随机字符串 此代码将创建一个可阅读的字符串,使其更接近词典中的单词,实用且具有密码验证功能. /*** ...
- JS实现new关键字的功能
一.前言 众所周知:没有对象怎么办?那就new一个! 那么在JS中,当我们new一个对象的时候,这个new关键字内部都干了什么呢? 现在我们就来剖析一下原生JS中new关键字内部的工作原理. 二.原始 ...
- 【转】JVM--内存区域划分
[原文地址]https://blog.csdn.net/sd4015700/article/details/50109939 Eden Space.Survivor Space.Tenured Gen ...
- vue 定义全局函数和变量
背景 最近我在整一个网站,介绍一些有意思的网站和实用工具的网站并且把他们收集起来,网站刚建有些不成熟希望给点意见 我用的是前端框架的vue, 但是我没有打包,直接甩到服务器上了, 不想扯了, 步骤 1 ...
- Spring 初始化流程
开始 在SpringIOC中,前面讲述了如何配置BeanDefinition和如何注册BeanDefinition,但是这些知识容器初始化的一部分,在AbstractApplicationContex ...
- Haar小波分析
一 尺度函数与小波函数 基本尺度函数定义为:,对其向右平移任意 k 个单位,构成函数族 , 该函数族在 空间中正交,证明如下: 1 : 2 当 m 不等于 k 时, 函数族 构成一组正交基,并形成 ...
- [LeetCode]1313. 解压缩编码列表
给你一个以行程长度编码压缩的整数列表 nums . 考虑每对相邻的两个元素 [freq, val] = [nums[2i], nums[2i+1]] (其中 i >= 0 ),每一对都表示解压后 ...
- Java中邮件发送session.getDefaultInstance和getInstance的区别
假设你想要同时用两个邮箱分别给再给两个邮箱发送邮件时,你就需要创建两个java.mail.Session对象,这时候你用getDefaultInstance的话会发现第二个Session对象和第一个对 ...
- 【转】可见性、原子性和有序性问题:并发编程Bug的源头
如果你细心观察的话,你会发现,不管是哪一门编程语言,并发类的知识都是在高级篇里.换句话说,这块知识点其实对于程序员来说,是比较进阶的知识.我自己这么多年学习过来,也确实觉得并发是比较难的,因为它会涉及 ...
- 一键生成的BI智能数据看板谁不爱?
随着互联网思维的深化,如财务.市场.运营.销售等越来越多的岗位,都开始重视并自发性的开始了解并学习数据分析,来引导帮助决策. 人力资源制定效能仪表盘,去实时掌握人员状况和人均效能,通过对招聘漏斗的分析 ...