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. 文心一言 VS 讯飞星火 VS chatgpt (62)-- 算法导论6.5 1题

    文心一言 VS 讯飞星火 VS chatgpt (62)-- 算法导论6.5 1题 一.试说明 HEAP-EXTRACT-MAX在堆A=(15,13,9,5,12,8,7,4,0,6,2,1)上的操作 ...

  2. 在线编辑Word——插入内容控件

    内容控件是可添加和自定义的以在模板.窗体和文档中使用的单个控件.Word中支持添加多种类型的控件用于不同文档的设计需求.本文,将通过在线编辑的方式展示如何在Word中插入内容控件,这里使用的在线编辑器 ...

  3. 二进制SCA指纹提取黑科技:Go语言逆向技术

    摘要:SCA(Software Composition Analysis)软件成分分析,指通过对软件源码.二进制软件包等的静态分析,挖掘其所存在的开源合规.已知漏洞等安全合规风险,是一种业界常见的安全 ...

  4. 华为云企业级Redis:助力VMALL打造先进特征平台

    摘要:当电商平台对AI算法模型的需求越来越多,特征数据平台的统一建设是不少开发团队头疼的事情.因为只有通过统一的特征数据存储,才能改变原有的"数据孤岛",解决生产重复造轮子的窘境. ...

  5. 8款最佳实践,保护你的 IaC 安全!

    基础设施即代码(IaC) 是一种快速发展的技术,利用软件开发原则和实践,用软件配置基础设施.与传统的 IT 基础架构相比,IaC 可以更高效地交付软件.自动化还解锁了弹性配置的能力,该功能可在不同的负 ...

  6. Solon 拉取 maven 包很慢或拉不了,怎么办?

    注意:如果在 IDEA 设置里指定了 settings.xml,下面两个方案可能会失效.(或者直接拿"腾讯" 的镜像仓库地址,按自己的习惯配置) 1.可以在项目的 pom.xml ...

  7. 将镜像上传到Docker Hub中央仓库中

    首先创建一个镜像,点击:创建一个简单的Docker镜像 1.先注册帐号 https://hub.docker.com/ 2.将镜象推上去 [root@localhost docker]# docker ...

  8. ThreadPoolExecutor 介绍

    线程池能够带来3个好处: 降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗:提高响应速度:当任务到达时,任务可以不需要等到线程创建就能立即执行:提高线程的可管理性:线程是稀缺资源,如 ...

  9. SpringBoot Scheduled 常见用法

    外部统一管理可用 xxl-job ,将各定时任务集中管理,灵活改变执行频率,支持某一个定时器集群处理,避免多服务启动时,每个服务都执行(重复执行) 比如我的API服务里有一个定时任务,将API做成集群 ...

  10. 背景 | 基于 Transformers 的编码器-解码器模型

    !pip install transformers==4.2.1 !pip install sentencepiece==0.1.95 Vaswani 等人在其名作 Attention is all ...