JVM GC排查-JFR
yuyutoo 2025-01-02 20:11 1 浏览 0 评论
现象:
2024-09-12日发现某api服务GC告警明显增多
从告警上看,不仅GC次数有明显变多,更严重的是GC耗时很长,并且都是FullGC。
分析:
从告警上看,现象就是FullGC,先找一台机器(xx.xx.13.141)看看,在15:37和16:06分别有一次FullGC告警,看一下15:37的GC日志,确实发生了一次1.05s的FullGC
为什么会发生FullGC?一定是Old区内存都不够了,继续查看这次GC之前的日志(如下图),可以看到在Concurrent Cycles期间因为 humongous object的原因内存一直在涨,最终内存不足,导致FullGC。
继续查看这个时间点早一点的日志(下图)也能发现Concurrent Cycles也是因为humongous object分配导致。
因为G1的MixGC不会一次回收所有垃圾,而是会进行多次MixGC回收垃圾直到垃圾占比小于阈值(5%),如果某个时间段大对象分配频繁,就会发生MixGC跟不上分配节奏导致内存耗尽。
我们初步判断是因为大对象导致本次FullGC,dump内存看看大对象是什么?(RegionSize=2M,超过1M就是大对象)
从图上可以看出大对象基本分为两类:
第一类:
通过RequestMapping OkHttpClient关键词能搜到对应代码如下,就是一个http请求的拦截器打印日志
第二类:
看起来像Json,仔细看又不太像,因为这个字符串内有class的名称,一般json是没有className的,但是我们仍然基本能确定是跟Customer对象相关的。
我们现在已经确定了这些大对象都是 byte[], char[] 数组,但是怎么确定这些对象是因为哪行代码分配的呢?我们可以关闭日志打印,但是治标不治本,必须弄清楚这些大对象是否合理,如果不知道相关的代码我们没办法进一步排查和修复。
这里给出3种可以尝试的方式:
- 关键词搜代码:通过字符串内的特殊片段结合项目的业务去搜索和猜测相关代码,如果对项目的业务熟悉这种方式也很快能找到对应代码。
- dragonwell:是alibaba的一款JVM,这个JVM有一个增强的功能,对于基本数据类型的大数组分配,可以打印分配的堆栈,这样我们能准确的找到对应的代码。见 阿里巴巴Dragonwell8用户指南
- 通过JFR观察 outside 的TLAB分配记录
知识加油站:什么是TLAB?
我们知道堆内存是所有线程共享的,如果在堆上分配内存需要加锁。
为了提高性能,JVM会给每个线程单独分配一块自己的内存,这块内存就叫TLAB,线程分配对象时用自己的TLAB不需要加锁,当TLAB不够时,去堆再申请一块TLAB(要加锁)。
假设下面的场景,TLAB=100kb,已经使用90kb,如果要分配对象11kb,那么这时候怎么办?
很明显TLAB剩下的10kb不足以分配给11kb的对象,如果重新申请一个新的TLAB,那么就会导致10kb的空间浪费,为了平衡性能和空间浪费,JVM有一个TLAB最大浪费阈值(refill waste),10kb > refill waste就不会申请新的TLAB,而是11kb的对象直接在堆上分配,如果 10kb < refill waste,那么就会申请一块新的TLAB,10kb就浪费掉。
在JFR中可以持续观察TLAB的分配情况,Allocation in new TLAB表示在TLAB中分配,Allocation outside TLAB表示在TLAB外分配。
JFR如何使用?大家自行搜索
因为我们要找的对象是大对象,我们知道G1中大对象是在H区直接分配的,不可能使用TLAB,因此我们通过JFR查看Allocation outside TLAB事件大概就能知道大对象分配时的堆栈了。
先使用jcms命令收集JFR信息:maxage:保留多长时间的,maxsize:文件大小
jcmd pid JFR.start name=1.jfr maxage=1h maxsize=400MB
使用JDK Mission Control工具(JDK自带)打开jfr文件,打开事件浏览器,找到Allocation outside TLAB,选中一个大对象查看堆栈
具体的堆栈如下:
简单梳理一下调用链路:
ShopCloseController.checkCloseShop
-> StockService.getStock
-> StockService.removeVirtualGoods
-> PssService.getVirtualGoods()
-> ApiResponse com.sun.proxy.$Proxy369.getChannelMihomeGoodsMap(Integer)
-> 日志打印
对应的源代码如下:
也就是说 getChannelMihomeGoodsMap 方法返回值太大了,我们自定义的OkHttp拦截器打印日志就造成了大对象。
拿一个具体的大字符串看看长什么样:看起来是这个接口返回了所有渠道的所有sku信息,这个字符串非常大,另外在打印日志时底层的StringBuilder又扩容导致最后的char[]非常巨大。
实际上我们并不需要所有渠道的sku,只需要一个渠道的sku即可,因此后续要替换接口。
对于第二种1M左右的大对象,从堆栈上很容易看到是 CustomerController.getDetail(long, int) 方法查了缓存,缓存查出来的就是byte[](后怕:redis缓存在么大的key,也不怕把redis搞死)
通过源码也能看到,和上面的堆栈一致。
也就是说Customer对象序列化的byte[]数组太大了,虽然下图的字符串不完整,但是我们也能大概猜到是因为Customer的address太多导致,从图中能看到这个用户的id,
数据库查一下这个用户的地址有1501个,确实是地址太多了。
实际上从JFR上看,大对象不仅仅是上面这两种,还有因 InvoiceController.getList 底层返回数据多导致的大对象和 BranchCompanyController.list 缓存大导致的大对象,这里就不一一分析了。
总结
整体思路:
(1)为什么会GC?通过gc日志发现是大对象频繁分配导致
(2)大对象是什么?通过jmap dump内存后查看发现是日志打印的string等
(3)怎么修复?需要确定大对象从哪分配
(4)大对象在哪分配?通过JFR查看大对象分配时的堆栈,找到对应代码
(5)怎么修复?接口返回的数据减少不必要的数据
实际上从今年开始,我主导组内所有项目的GC治理开始,就发现了大对象是一个普遍现象,根本原因有二:
- 协议缺陷:服务之间调用使用http协议,参数和返回值都是string,一旦某些接口数据量大,必然会导致大的string对象,从现象来看都是某个对象有一些集合字段,集合太大,json序列化后的string就很大,二进制的序列化就不会有这个问题。
- 意思薄弱:开发人员在性能方面的意识非常薄弱,只管实现功能,至于性能怎么样压根不关心,以至于今年我在做GC分享的时候,有个leader问我你gc耗时30ms,我服务gc耗时1s还不是照样跑得好好的这样的问题。另外对于批量接口压根就不做数量的限制,基本都是全量数据直接扔出去。
相关推荐
- C语言头文件中 #ifndef #define #endif 的作用
-
头文件的第一个功能就是#include指令,即在预编译时把它后面所写的那个文件的内容,完完整整地一字不改地包含到当前的文件中来。多次包含相同的头文件,会导致编译器多次编译该头文件,代码量小还好,代...
- SpringBoot中静态变量注入方案,一网打尽
-
前言Hi,大家好,我是麦洛,昨天同事来找我。说自己想使用@Value注解来注入值,但是发现注入不进去,想让我帮忙看看。研究了一番,最后发现是@Value注解无法注入静态变量。下面我们一起来回顾一下本次...
- java各种类型变量你还不傻傻分不清?最全的变量都在这
-
前言互联网上有很多关于变量的解释。质量参差不齐,所以我写了这篇文章和我的理解。如果有什么不对劲的地方,速速指教。变量是我们经常使用的一种类型。当我第一次学习Java时,我经常被各种变量的概念所折磨。那...
- (建议收藏)关于单片机检查变量的方法,你会几种?
-
这些单片机调试方法你真的知道吗?导读:授人以鱼不如授人以渔,为什么那些前辈们能快捷定位问题,这个系列的文章将揭秘KEIL调试那些不为人知的事。以下内容更适用于STM32单片机(51也支持局部...
- Excel VBA入门教程1.2 常量和变量
-
定义后不能被改变的量,就是常量;相反的...
- "两组"连续变量之间的相关分析(SPSS:典型相关分析)
-
典型相关分析的基本思路:首先采用类似主成分分析的方法,在两组连续变量中分别提取变量的线性组合(综合变量),使两组的综合变量间具有最大的相关性。然后在两组连续变量中分别提取第二对线性组合,使提取的综合变...
- CPU眼里的:静态、全局、临时变量
-
“静态、全局、临时变量,它们有什么区别?为什么要把变量分成这么多的类别?这么做的意义在哪里?有什么好处呢?...
- C语言变量的初始值,隐藏很多危险?如何危险?
-
变量的初始值局部变量如果没初始化,那么,变量的值是创建变量,申请内存空间的时候,内存空间存放的数据。所以,局部变量得到的数据是一个随机值。就是变量空间中,存放的原始内存数据。全局变量如果没有初始化,那...
- C语言里的static变量其他语言是看不上还是学不去?
-
C语言里的static变量其他语言是看不上还是学不去?static变量在C语言中被用于具有静态存储期的局部变量或全局变量。它有以下几个特点:1.静态存储期:static变量在程序执行时分配内存,直到...
- 静态变量是在什么时候被加载的?是编译器还是在运行期?
-
静态变量的加载过程一般情况下是发生在在程序运行时的初始化阶段。具体来讲是在程序运行的时候,当类被第一次被加载到内存中的时候。这也就是是说,一个静态变量的生命周期是从类第一次被加载到内存时开始,直到程序...
- C语言的随机数函数和静态变量
-
ANSI-C库提供了rand()函数生成随机数。生成随机数有多种算法,ANSI-C允许C实现针对特定机器使用最佳算法。然而,ANSI-C标准还提供了一个可移植的标准算法,在不同系统中生成相同的随机数。...
- 「C++学习笔记(十)」理解类中的静态成员变量与静态成员函数
-
一.类的静态成员大家应该都知道静态变量,就是在变量前面加上static,类的静态成员也是同样的道理,在类的成员函数与成员变量前面加上static声明为类的静态成员。和静态变量的原理一样,不管这个类创...
- CPU眼里的:变量
-
“变量,是所有编程语言的基本元素,但变量的物理意义,你有了解过吗?是的,没有物理意义,变量的语法意义将荡然无存!...
- 【VBA基础】变量的类型和申明变量类型的重要性
-
各位朋友,你们好,今天和你们聊点VBA基础知识:VBA变量。注:此文字数约3500字,主要是讲解VBA变量知识,通过一些动图展示变量上的效果;如果你是初学者,建议先收藏此文,看看最后给初学者的建议那一...
- 变量,还有这些秘密
-
1、变量的意义任何一门高级开发语言,都离不开变量。通过变量,编程语言才能搬运和表达我们的小心思。变量是机器指令语义化的第一步,却是高级开发语言进化的一大步。2、变量的起源在计算机编年史里的蛮荒时代,人...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- mybatis plus (70)
- scheduledtask (71)
- css滚动条 (60)
- java学生成绩管理系统 (59)
- 结构体数组 (69)
- databasemetadata (64)
- javastatic (68)
- jsp实用教程 (53)
- fontawesome (57)
- widget开发 (57)
- vb net教程 (62)
- hibernate 教程 (63)
- case语句 (57)
- svn连接 (74)
- directoryindex (69)
- session timeout (58)
- textbox换行 (67)
- extension_dir (64)
- linearlayout (58)
- vba高级教程 (75)
- iframe用法 (58)
- sqlparameter (59)
- trim函数 (59)
- flex布局 (63)
- contextloaderlistener (56)