1. Full GC (Ergonomics)

1.1 Java 进程一直进行 Full GC

例行检查线上运行的 Java 服务,通过 jstat -gcutil < pid > 命令检查 gc 情况的时候发现一个服务有点异常。可以看到以下打印的 gc 情况中,只有 FGC 的次数一直在变化,而YGC维持不变,也就是说这个服务一直在进行 Full GC,显而易见是有问题的

S0 S1 E O M CCS YGC YGCT FGC FGCT GCT
0.00 0.00 100.00 99.97 90.48 88.34 17498 91.739 1452 570.310 662.049
0.00 0.00 13.47 99.97 90.48 88.34 17498 91.739 1453 571.179 662.918
0.00 0.00 39.33 99.97 90.48 88.34 17498 91.739 1454 571.879 663.118

1.2 Full GC 的原因

检查 gc 日志,发现有以下 log,可以看到发生 Full GC 的原因是 Ergonomics,并且年老代 Full GC 前后占用的内存几乎不变。查找资料,发现当使用 Server 模式下的ParallelGC 收集器组合(Parallel Scavenge + Serial Old)时,会在 Minor GC前进行一次判断,也就是 内存空间分配担保机制

  • Eden 空间不足发生 Minor GC 之前,虚拟机先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果条件成立的话,那么 Minor GC 可以确认是安全的。如果不成立的话虚拟机查看 HandlePromotionFailure 的值是否允许担保失败,如果HandlePromotionFailure 的值不允许冒险,那么就要进行一次 Full GC。如果允许则检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于将继续尝试进行 Minor GC,小于的话就要进行 Full GC以保证 Minor GC 的空间担保成功

这个 Java 服务没有通过启动参数配置垃圾收集器,查看堆配置发现是使用的默认 Parallel GC 。显然,合理的推测是空间分配担保失败导致了Full GC

[Full GC (Ergonomics) [PSYoungGen: 82944K->8916K(84992K)] [ParOldGen: 175101K->175101K(175104K)] 258045K->184018K(260096K), [Metaspace: 106250K->106166K(1153024K)], 0.4203767 secs] [Times: user=1.29 sys=0.00, real=0.42 secs]

1.3 检查堆占用

  1. 使用 jmap -heap 32006 命令查看堆内存使用情况,发现老年代的使用已经达到了 99.97697796737938%,只剩下了 0.03M,是可以和之前的推测相佐证的

    PS Old Generation
    capacity = 179306496 (171.0MB)
    used = 179265216 (170.96063232421875MB)
    free = 41280 (0.03936767578125MB)
    99.97697796737938% used
    • 1
    • 2
    • 3
    • 4
    • 5
  2. 使用命令 jmap -histo 32006 | head -n 30 查看堆内存中占用内存最多的前 30 个类实例,发现可疑的类有以下 3 个。其中 SpringValue 为携程开源框架 apollo 的内部类,LinkedListMultimap是 apollo 引用的 google 开源的集合类,像这种开源的代码如果有内存泄露的问题估计早就被曝出来修复掉了,所以唯一的疑点就是项目内部自己实现的 MessageProcessor 这个类了

    1. com.ctrip.framework.apollo.spring.property.SpringValue --53万个对象,占用 23M
    2. com.google.common.collect.LinkedListMultimap$Node --53万个对象,占用 21M
    3. com.service.task.client.MessageProcessor – 53万个对象,占用 17M

2. 代码检查

检查代码,发现 MessageProcessor 类通过注解 @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)修饰,每次使用的时候都会新建一个对象,其内部还通过注解 @Value 引用了 apollo 配置。这个类的功能是从消息队列中拉取消息,然后将其分发给处理函数,从而完成一次消息处理。这个类之所以被设计成多实例,可以参考Spring 多实例注入,没错,就是笔者自己写的,因此原因也很清楚了

  • 脚本每被调度一次,MessageProcessor 就创建一个新实例用于从指定的消息队列中拉消息。这样时间一长,MessageProcessor对象大量被创建,堆积在堆内存年轻代中,触发 Minor GC。本来这些只使用一次的对象理应在多次 Minor GC 中慢慢被回收掉,但是 JVM 的动态年龄机制是如果在 Survivor 中相同年龄所有对象大小的总和大于 Survivor 空间的一半, 则年龄大于或等于该年龄的对象可以直接进入老年代,这样大量的MessageProcessor对象就跳过了年龄限制,直接进入老年代,导致老年代对象占用的内存居高不下。这种情况下当 Minor GC触发时,由于老年代剩余内存空间不足,空间担保必然失败,就触发了 Full GC (Ergonomics)

3. 解决方式

  1. 使用多实例注入的思路是没错的,错误在于笔者的使用方式。之前的使用方式在脚本启动的时候就会新建 MessageProcessor 对象,造成大量的重复对象被创建,不仅浪费了内存,还会在一定程度上影响性能。基于此解决的方法很简单,只要通过@Bean(name = "xxxx")注解为每条队列单独配置好一个 processor 消息处理者,再使用@Resource(name = "xxxx")引用指定的 MessageProcessor 对象,避免大量相同的MessageProcessor对象被创建出来就可以了
  2. 使用多实例注入的实质是为了解决 MessageProcessor对象中某些属性的线程隔离问题,故也可以使用单例MessageProcessor对象,同时将需要隔离的属性存入 ThreadLocal 的解决方法。最后,最简单粗暴的解决方式是,将需要隔离的属性直接方法入参,这样就不需要考虑线程隔离问题了
文章知识点与官方知识档案匹配,可进一步学习相关知识
Java技能树首页概览118326 人正在系统学习中

【转帖】Java Full GC (Ergonomics) 的排查的更多相关文章

  1. Java程序线上故障排查

    目录 一.Linux 内存和cpu 网络 磁盘 /proc文件系统 二.JVM Java堆和垃圾收集器 gc日志分析 JVMTI介绍 Attach机制 java自带工具 三.三方工具 jprofile ...

  2. JVM学习(4)——全面总结Java的GC算法和回收机制

    俗话说,自己写的代码,6个月后也是别人的代码……复习!复习!复习!涉及到的知识点总结如下: 一些JVM的跟踪参数的设置 Java堆的分配参数 -Xmx 和 –Xms 应该保持一个什么关系,可以让系统的 ...

  3. JVM学习(4)——全面总结Java的GC算法和回收机制---转载自http://www.cnblogs.com/kubixuesheng/p/5208647.html

    俗话说,自己写的代码,6个月后也是别人的代码……复习!复习!复习!涉及到的知识点总结如下: 一些JVM的跟踪参数的设置 Java堆的分配参数 -Xmx 和 –Xms 应该保持一个什么关系,可以让系统的 ...

  4. Java诊断利器Arthas优雅排查生产环境

    前言 Arthas 是Alibaba开源的Java诊断工具.在线排查问题,无需重启:动态跟踪Java代码:实时监控JVM状态.对分秒必争的线上异常,Arthas可帮助我们快速诊断相关问题. 下载安装 ...

  5. java:线上问题排查常用手段(转)

    出处:java:线上问题排查常用手段 一.jmap找出占用内存较大的实例 先给个示例代码: import java.util.ArrayList; import java.util.List; imp ...

  6. Java -verbose:gc 命令

    Java -verbose:gc 中参数-verbose:gc 表示输出虚拟机中GC的详细情况. [Full GC 168K->97K(1984K), 0.0253873 secs]   解读如 ...

  7. Java进程CPU使用率高排查

    Java进程CPU使用率高排查 生产java应用,CPU使用率一直很高,经常达到100%,通过以下步骤完美解决,分享一下.1.jps 获取Java进程的PID.2.jstack pid >> ...

  8. JAVA 从GC日志分析堆内存 第七节

    JAVA 从GC日志分析堆内存 第七节   在上一章中,我们只设置了整个堆的内存大小.但是我们知道,堆又分为了新生代,年老代.他们之间的内存怎么分配呢?新生代又分为Eden和Survivor,他们的比 ...

  9. Java之GC

    Java之GC GC:GC 是JVM的垃圾回收器.与C/C++不同,java程序员无需考虑太多内存分配的位置,更不用考虑内存释放的机制,java对象内存的申请和释放都有JVM托管.JVM的内存释放机制 ...

  10. Java的GC

    垃圾收集 在探究Jvm的过程中,有两个点特别需要关注,一是:内存的使用,分配策略,而这一点是在前一篇博客已经介绍过了. 二是:内存的回收.也就是这一篇博客所要探究的关键点. 内存回收需要关注的几个点: ...

随机推荐

  1. IoT与鸿蒙、低代码、生成式AI,引爆技术浪潮——华为云开发者日南京站成功举办

    本文分享自华为云社区<IoT与鸿蒙.低代码.生成式AI,引爆技术浪潮--华为云开发者日南京站成功举办>,作者:华为云社区精选 . 近日,华为云开发者日HDC.Cloud Day南京站成功举 ...

  2. 看图学NumPy:掌握n维数组基础知识点,看这一篇就够了

    摘要:NumPy是Python的最重要的扩展程序库之一,也是入门机器学习编程的必备工具.国外有位程序员讲NumPy的基本运算以图解的方式写下来,让学习过程变得轻松有趣. NumPy是Python的最重 ...

  3. CPU高速缓存与极性代码设计

    摘要:CPU内置少量的高速缓存的重要性不言而喻,在体积.成本.效率等因素下产生了当今用到的计算机的存储结构. 介绍 cpu缓存的结构 缓存的存取与一致 代码设计的考量 最后 CPU频率太快,其处理速度 ...

  4. 跟我学ModelArts丨探索ModelArts平台个性化联邦学习API

    摘要:ModelArts提供了一个实现个性化联邦学习的API--pytorch_fedamp_emnist_classification,它主要是让拥有相似数据分布的客户进行更多合作的一个横向联邦学习 ...

  5. 一文带你认识MindSpore新一代分子模拟库SPONGE

    [本期推荐专题]物联网从业人员必读:华为云专家为你详细解读LiteOS各模块开发及其实现原理. 摘要:基于MindSpore自动并行.图算融合等特性,SPONGE可高效地完成传统分子模拟过程,利用Mi ...

  6. CG行业云渲染服务的演进之路

    摘要:影视动画.特效制作等行业渲染需求量增多,4K/6K以及各高分辨率会陆续成为主流,本地算力与存储资源已无法满足现有任务量.而随着大环境的演变,CG行业发展已进入发展快车道.来自赞奇科技的CEO金伟 ...

  7. pip升级和卸载安装的第三方库

    pip install --upgrade 第三方库名 pip uninstall 第三方库名

  8. SQL Server 项目中 SQL 脚本更新、升级方式,防止多次重复执行

    MySQL 项目中 SQL 脚本更新.升级方式,防止多次重复执行 Oracle 项目中 SQL 脚本更新方式 一套代码,多家部署时,在SQL脚本升级时,通过一个SQL文件给运维,避免出现SQL执行序顺 ...

  9. Intellij IDEA 开启 RunDashboard

    1. 关闭 Intellij IDEA (2018以下的版本无效),打开 workspace.xml 找到  RunDashboard 节点.添加如果配置 <option name=" ...

  10. C# 和 java 基本数据类型

    C# 和 java 基本数据类型 C#类型 java类型 描述 默认值 bool boolean 布尔值 False byte byte 8 位无符号整数 0 char char 16 位 Unico ...