线上环境

JDK1.8 使用默认垃圾收集器:ParallelGC

服务器内存32G(部署了多台服务)

线上启动JVM时,未添加任何参数配置。

所以默认配置为:

Non-default VM flags: -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=null -XX:InitialHeapSize=526385152 -XX:MaxHeapSize=8392802304 -XX:MaxNewSize=2797600768 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=1572864 -XX:OldSize=524812288 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC

初始化内存大小:500M(1/64),最大内存:8G(1/4)。

问题现象

服务器运行一段时间后,可以看出FGC次数频繁且耗时长,104次FGC,耗时达到258秒,平均每次FGC持续时间2秒多。

当前内存使用情况

Eden区2G多,老年代5G多,内存被大量消耗,实际上根本不需要如此大的内存,老年代中存在大量的朝生夕死的对象。

内存消耗大

正是因为ParallelGC的自动调节,造成了内存不足的假象,再加上没有控制堆的最大值,导致老年代的内存被无限放大到8G,严重浪费了服务器的内存资源。


目前线上运行状态已经清楚了,分析一下问题吧!

1、首先是关于JVM启动参数问题

JVM启动时,有一些参数是必须要配置的,不然出了问题很难定位

日志类:

从GC日志中,可以看出每次GC前后的内存大小对比,从而帮助分析内存大小设置的合理性,以及是否有内存泄露等问题存在。

-XX:+PrintGCDetails(输出GC的详细日志)

-XX:+PrintGCDateStamps(输出GC的时间戳)

-Xloggc:/opt/xxx/logs/xxx-xxx-gc-%t.log(日志文件的输出路径)

OOM后生成快照:

产生OOM后,需要通过堆的快照分析具体导致OOM的原因。

-XX:+HeapDumpOnOutOfMemoryError (OOM时生成Dump文件)

-XX:HeapDumpPath=/memory.hprof(OOM文件生成地址)

JVM堆内存参数:

合理的堆内存配置,可以帮助减少FGC的次数和时长。

-Xmn(新生代大小)

-Xms (初始分配内存,默认为物理内存的1/64)

-Xmx (最大分配内存大小,默认为物理内存的1/4)

-XX:SurvivorRatio(Eden区和Survivor区比例,默认8:1:1)

默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制;空余堆内存大于70%时,JVM会减少堆直到 -Xms的最小限制。

因此服务器一般设置-Xms、-Xmx相等以避免在每次GC后调整堆的大小。调大导致FGC时间变长,调小又会导致FGC频繁。

2、ParallelGC垃圾收集器问题

现在大多数生产环境中都用的是JDK8,并且使用默认的垃圾收集器ParallelGC。

使用ParallelGC要特别注意AdaptiveSizePolicy参数的问题,还是上面那张图,看看Eden和Survivor区的分配占比,明显不是8:1:1了,这就是因为AdaptiveSizePolicy动态调整的原因。

AdaptiveSizePolicy 有三个目标:

  • Pause goal:应用达到预期的 GC 暂停时间。

  • Throughput goal:应用达到预期的吞吐量,即应用正常运行时间 / (正常运行时间 + GC 耗时)。

  • Minimum footprint:尽可能小的内存占用量。

AdaptiveSizePolicy 为了达到三个预期目标,涉及以下操作:

  • 如果 GC 停顿时间超过了预期值,会减小内存大小。理论上,减小内存,可以减少垃圾标记等操作的耗时,以此达到预期停顿时间。

  • 如果应用吞吐量小于预期,会增加内存大小。理论上,增大内存,可以降低 GC 的频率,以此达到预期吞吐量。

  • 如果应用达到了前两个目标,则尝试减小内存,以减少内存消耗。

所以为了达到期望的目标,Survivor区被调整的很小,导致新生代的对象被大量的移到了老年代了。
又由于每次FGC后老年代空间被动态调整的问题,导致老年代空间越来越大,同时也就意味着一次FGC的时间将会变得越来越长。

解决问题

问题已经分析出来了,那么应该如何解决呢?

  1. -XX:SurvivorRatio,指定比例Eden区和Survivor的比例,不要让AdaptiveSizePolicy动态调整。

  2. 合理控制老年代大小,对于ParallelGC这样的垃圾收集器,老年代空间越大,一次FGC的停顿时间就越长。

  3. 控制新生代大小,新生代一般可以适当调大一些,让那些朝生夕死的对象能够全部在新生代被回收。

    堆内存到底设置多大合适?这个一般要根据线上的实际使用情况来决定,其实如果不存在内存泄露问题,则只需从每次gc的后存活对象的大小,就能大致估算出实际所需要的内存空间(GC日志的重要性)。

比如下面这个,是刚刚经历过一次FGC的服务(频率大概半小时一次),新生代使用159M,老年代使用78M,所以很明显,根据FGC的频率和回收完之后的内存使用大小,可以看出这个项目根本用不到多少内存,为了用来应对系统峰值时的业务量激增导致产生的对象也激增的场景,再做一些适当的冗余,所以新生代500M,老年代1G足矣。

垃圾收集器之所以要分代就是为了能够快速的把一些朝生夕死的对象给处理掉,如果Survivor小到形容虚设,就失去了分代收集的意义,因为每次Eden区的对象只要能熬过一次YGC就会被放到老年代(Survivor区太小不够放),实际上可能在第二次YGC时就可以回收了,对于ParallelGC这样的垃圾收集器,对象一旦进入老年代就只能等待内存100%后触发FGC才会被回收了。

JVM调优的目标之一就是要减少FGC的次数。

最简单的解决方式

其实处理这类问题,需要你对JVM垃圾回收方面有一定的了解,并且随着业务的发展可能需要持续的观察并调整堆空间的大小分配,所以最简单的方式就是直接换G1即可。

记录一次使用ParallelGC导致线上FGC频繁、耗时长的原因相关推荐

  1. 线上FGC调优案例三则

    前言 闲鱼服务端应用广泛使用 Java 技术栈,基于JVM提供的托管式堆内存管理,开发者无需过多关心对象创建/回收时的内存分配/释放动作,垃圾回收器(Garbage Collector)会在需要的时候 ...

  2. 线上FullGC频繁的排查

    线上FullGC频繁的排查 问题 前段时间发现线上的一个dubbo服务Full GC比较频繁,大约每两天就会执行一次Full GC. Full GC的原因 我们知道Full GC的触发条件大致情况有以 ...

  3. 一次jvm导致线上内存占用过高问题定位

    背景:8G物理内存,8核CPU,jvm使用的G1垃圾回收器. 问题:线上内存占用告警,内存占用超过85%,且现象一直持续. 分析 看一下jvm启动参数配置: -Xms6144m -Xmx6144m - ...

  4. 基本类型为空导致线上空指针异常问题 java.lang.NullPointerException: cannot unbox null value

    线上钉钉群突然报空指针异常,结合日志分析代码,如下: 报错信息:(含入参) reQueryDto={\"minAge\":null,\"maxAge\":26, ...

  5. 万万没想到! logger.info() 还能导致线上故障?

    事故代码 直入主题,生产环境日志级别为warn,请看如下这行代码: LOGGER.info("the DTO info: {}", JSON.toJSONString(DTO)); ...

  6. GC导致线上CPU超100%

    运维同学告知报警,一个java进程占用CPU 100%以上.下面进行排查步骤. 1.top -c ,效果如下图(这个截图是另一个节点的效果,实际进程号为17817,后续的截图将都会以进程号17817为 ...

  7. mysql内存爆_线上MySQL机器内存爆掉原因分析与解决

    现象: 阿里金融某业务的MySQL机器的内存每隔几天就会增长,涨上去后,却不下来.累积后内存爆掉. 分析: 此业务是间隔的对MySQL有大访问,其它时间几乎无访问.排查发现,内存涨时,一般会有MySQ ...

  8. 线上Mysql数据库崩溃事故的原因和处理

    前文提要 承接前文<一次线上Mysql数据库崩溃事故的记录>,在文章中讲到了一次线上数据库崩溃的事件记录,建议两篇文章结合在一起看,不至于摸不着头脑. 由于时间原因,其中只讲了当时的一些经 ...

  9. 重大事故!线上系统频繁卡死,凶手竟然是 Full GC ?

    每日鸡汤,好喝 01 案发现场 通常来说,一个系统在上线之前应该经过多轮的调试,在测试服务器上稳定的运行过一段时间.我们知道 Full GC 会导致 Stop The World 情况的出现,严重影响 ...

  10. 线上环境频繁GC问题排查,Finalizer对象该背这个锅吗?

    问题描述 公司的一个SpringMVC服务,最近在做运维检查的时候发现young gc 和 full gc太频繁,远远超过了正常情况.服务器配置是4核8G,该服务分配了6G内存.通过arthas的da ...

最新文章

  1. 论文不公开代码,应该被直接拒稿?
  2. Node.js调用C#代码
  3. ios JSON 解析流程(转)
  4. python中的模块原则_python 的模块与包
  5. UFLDL教程:Exercise:Vectorization
  6. getbean方法找不到bean_iphone手机静音找不到怎么办 iphone静音找不到解决方法【图文】...
  7. [蓝桥杯2018初赛]次数差-模拟,map容器
  8. php声波模拟开门,关于 php使用扩展控制树莓派io 驱动超声波测距
  9. c语言程序女设计教学效果分析,C语言程序设计的教学论文
  10. [区块链] 密码学——Merkle 树
  11. mysql数据库写入数据的语法_mysql数据库插入数据语法
  12. 【线程】——等待集(wait,notify,notifyAll)
  13. Linux指纹识别程序,linux上应用指纹识别(转载)
  14. java csv转owl_数据处理第2节:将列转换为正确的形状
  15. c++ packaged_task
  16. 多校区网络直播系统解决方案
  17. 可能是因为该宏在此工作簿中不可用,或者所有的宏都被禁用
  18. 华安基金高管事发 基金业突遇“公信力寒流”(ZT)
  19. Windows XP 搭建PPPoE服务器_计算机软件及应用_IT/计算机_专业资料
  20. Spring + iBATIS完整示例

热门文章

  1. 抖音小程序是什么_如何开发抖音小程序
  2. mac 谷歌浏览器翻译问题
  3. python处理grd格式文件_Surfer的grd文件格式说明
  4. 【K8S集群安装二】K8S集群安装步骤
  5. 聚类分析通俗易懂解释
  6. vscode输入特殊符号
  7. 干货分享:PDF分割合并工具免费哪个好用?
  8. 泰勒公式的展开细节解析
  9. win10微软拼音输入法无提示怎么办?
  10. matlab 傅立叶变换去噪