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. 解读 SSDB、LevelDB 和 RocksDB 到 GaussDB(for Redis) 的迁移

    摘要:本期将详细介绍 SSDB.LevelDB 和 RocksDB 到 GaussDB(for Redis)的迁移. 本文分享自华为云社区<华为云PB级数据库GaussDB(for Redis) ...

  2. 解读分布式调度平台Airflow在华为云MRS中的实践

    摘要:Airflow是一个使用Python语言编写的分布式调度平台,通过DAG(Directed acyclic graph 有向无环图)来管理任务. 本文分享自华为云社区<分布式调度平台Air ...

  3. DevSecOps软件安全开发实践

    摘要:DevSecOps 的理念是将安全防护流程有机地融入传统的 DevOps 流程中,为研发安全提供强有力保证,安全工具支撑研发阶段安全要求落地. 本文分享自华为云社区<DevSecOps软件 ...

  4. 用Python自动清理系统垃圾,再也不用360安全卫士了

    用Python自动清理系统垃圾,再也不用360安全卫士了 在Windows在安装和使用过程中都会产生相当多的垃圾文件,包括临时文件(如:.tmp.._mp)日志文件(.log).临时帮助文件(.gid ...

  5. 解决延迟有 Wi-Fi 6 就够了!

    最近二狗子家里的路由器坏了,而家里的数据网络信号又非常差,失去了路由器基本上就等于和世界隔离,所以二狗子打算去附近商城随便买一个新的路由器,结果售货员张口就问:"买 Wi-Fi 6 的路由器 ...

  6. MAVEN 配置和基本使用

    Maven 安装配置 解压 apache-maven-3.6.1.rar 既安装完成 配置环境变量 MAVEN_HOME 为安装路径的 bin 目录 在 Path 下添加 %MAVEN_HOME%\b ...

  7. L1-046 整除光棍 (20分)

    问题描述 这里所谓的"光棍",并不是指单身汪啦~ 说的是全部由1组成的数字,比如1.11.111.1111等.传说任何一个光棍都能被一个不以5结尾的奇数整除.比如,111111就可 ...

  8. 定向减免!函数计算让 ETL 数据加工更简单

    业内较为常见的高频短时 ETL 数据加工场景,即频率高时延短,一般费用大头均在函数调用次数上,推荐方案一般为攒批处理,高额的计算成本往往令用户感到头疼,函数计算推出定向减免方案,让 ETL数据加工更简 ...

  9. springboot 参数通过 @RequestBody传递的写法

    controller中有时候参数是使用 @RequestBody传递的这种怎么写? 直接上代码 @RequestMapping("/test") public void getAc ...

  10. 【转载】内存基本概念-watermark&lowmem_reserve

    概述 当系统内存短缺的情况下仍去申请内存,可能会触发系统对内存的回收,那什么时候应该进行回收,回收到什么标准又可以停止回收,参考依据是什么?即本文将介绍的watermark(内存水位线),当检查wat ...