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. 中国汽车工业协会 SDV 软件定义汽车服务 API 第三版下载

    开门见山,下载链接: https://files.cnblogs.com/files/tengzijian/SDV_API_Version3_Beta1_公众号:好记性如烂笔头.zip?t=16904 ...

  2. JavaScript异步编程4——Promise错误处理

    目录 1. 概述 2. 详论 3. 参考 1. 概述 在上一篇文章<JavaScript异步编程3--Promise的链式使用>中,通过Promise的链式使用,避免程序中多次嵌套回调(回 ...

  3. CUDA C编程权威指南:1.2-CUDA基础知识点梳理

      主要整理了N多年前(2013年)学习CUDA的时候开始总结的知识点,好长时间不写CUDA代码了,现在LLM推理需要重新学习CUDA编程,看来出来混迟早要还的. 1.闭扫描和开扫描   对于一个二元 ...

  4. 【福利活动】华为云“上云之路”征文大赛开启,FreeBuds3无线耳机等重磅好礼送不停

    各位关注华为云的开发者们,达嘎猴啊~ 今天带给你们一个好消息,大家心心念念的华为云"上云之路"征文大赛已经正式开启啦. 举办本次华为云"上云之路"征文大赛的目的 ...

  5. 如何通过jstat命令进行查看堆内存使用情况?

    摘要:jstat命令可以查看堆内存各部分的使用量,以及加载类的数量. 本文分享自华为云社区<JVM之通过jstat命令进行查看堆内存使用情况>,作者:共饮一杯无 . 基本概念 jstat是 ...

  6. 知识+AI融合创新探索,华为云论文被AI顶级学术期刊IEEE TPAMI接受

    摘要:通过利用物体类别之间存在的层级关系约束,自动学习从数据中抽取识别不同类别的规则,一方面对模型的预测过程进行解释,另一方面也提供了一条引入人工先验知识的可行途径. 前言 受益于深度学习技术的突破, ...

  7. 一文你带快速认识Vue-Router路由

    摘要:Vue Router是Vue.js 官方的路由管理器.它和Vue.js的核心深度集成,可以非常方便的用于SPA应用程序的开发. 本文分享自华为云社区<Vue-Router路由快速了解与应用 ...

  8. Django增删改查

    增删改查.配置对应路由,函数,视图.报错注意看控制台. 添加取到前台传来的参数,后端给予验证.入库 编辑,取到当前编辑得id,在后台查到对应数据.重新update 删除,取到当前点击ID,后台dele ...

  9. k8s-修改线程数

    1.背景: (1)胖容器ssh登录报错:handshake error (2)登录宿主机后,观察pod状态为running,但是kubectl exec 和docker exec 均无法进入该容器,报 ...

  10. 这应该是java最好用的orm之一了

    这应该是java最好用的orm之一了 说起orm大家肯定都不会陌生,作者是一个.net菜鸟.并且是在.net繁荣的orm圈子下成长的,所以这次给大家带来的是媲美efcore,freesql,sqlsu ...