纯干货:大对象导致FullGC频繁的原因及实践思路

2024-01-31 22:20

本文主要是介绍纯干货:大对象导致FullGC频繁的原因及实践思路,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

今天在检查线上环境的时候,发现了在2分钟内出现了2次FullGC。
虽然对线上功能影响不是很大,但还是想一探究竟。

线上监控得到的信息:
CAT监控
可以看到从短时间内有了2次GC,从13次直接飙到15次。

然后看了下老年代的堆情况:
在这里插入图片描述
可以看到这两次分别从620M直接下降到了400M然后又下降到了200M的样子。

脑海中的直觉应该是出现了大对象的感觉,因为老年代的堆是650M。达到620M触发GC,可能是堆空间不足,对象分配不进去,触发了1次GC,清理了200M,这个没什么问题,但是同一时刻又触发了一次GC,又清理了一遍,这个就是有问题了。

但是这个不是什么内存溢出啊啥的,dump不到大对象呀。事故现场已经被清理完了这可咋整喔 。

这个时候我在想,要是Full GC之前能够得到hprof文件就好了。

但其实JVM早就提供了这些参数了。

开启GC参数

# 1. 查看可实时配置的GC参数
java -XX:+PrintFlagsFinal -version | grep manageable
# 2. 查看服务进程编号
jps
# 3. 在full gc前开启dump文件 +表示开启 -表示关闭。 18881 代表应用进程编号
jinfo -flag +HeapDumpBeforeFullGC 18881
jinfo -flag HeapDumpPath=/elab/spring-boot/logs/dump_file 18881
# 查看配置是否生效
jinfo -flag HeapDumpPath 18881# 查看当前应用的jvm配置
jinfo -flags 18881

通过第一个命令可以不重启应用实时开启的参数:

java -XX:+PrintFlagsFinal -version | grep manageableintx CMSAbortablePrecleanWaitMillis            = 100                                 {manageable}intx CMSTriggerInterval                        = -1                                  {manageable}intx CMSWaitDuration                           = 2000                                {manageable}bool HeapDumpAfterFullGC                       = false                               {manageable}bool HeapDumpBeforeFullGC                      = false                               {manageable}bool HeapDumpOnOutOfMemoryError                = false                               {manageable}ccstr HeapDumpPath                              =                                     {manageable}uintx MaxHeapFreeRatio                          = 100                                 {manageable}uintx MinHeapFreeRatio                          = 0                                   {manageable}bool PrintClassHistogram                       = false                               {manageable}bool PrintClassHistogramAfterFullGC            = false                               {manageable}bool PrintClassHistogramBeforeFullGC           = false                               {manageable}bool PrintConcurrentLocks                      = false                               {manageable}bool PrintGC                                   = false                               {manageable}bool PrintGCDateStamps                         = false                               {manageable}bool PrintGCDetails                            = false                               {manageable}bool PrintGCID                                 = false                               {manageable}bool PrintGCTimeStamps                         = false                               {manageable}

这里我们关注其中几个参数:

  • PrintClassHistogramBeforeFullGC
    • 这个参数是说在full gc前会将内存中的对象以日志的形式输出,但是很多大对象都是些byte啊啥的,你压根不知道是那个对象引用的。
  • HeapDumpBeforeFullGC
    • 这个参数就是full gc前将hprof文件保存下来
  • HeapDumpPath
    • dump下来的hprof文件存放位置

通过上述操作,在应用下一次full gc的时候便会保存hprof文件文件

分析hprof文件

通过上述命令保存下来的文件大概有1.3G,有点大。

下载下来会比较麻烦。

这里通过MAT的linux的工具直接在服务器上进行分析。

MAT分析工具
从这个网站上下载Linux (x86_64/GTK+)

如何使用?
cd mat
./ParseHeapDump.sh /elab/spring-boot/dump.hprof  org.eclipse.mat.api:suspects org.eclipse.mat.api:overview org.eclipse.mat.api:top_components# 预计五分钟之后出结果,查看结果就到hprof所在的位置

/elab/spring-boot/dump.hprof : 位置

-rw-r--r-- 1 root root 8.3M Mar 20 10:15 java_pid18881.a2s.index
-rw-r--r-- 1 root root  13M Mar 20 10:15 java_pid18881.domIn.index
-rw-r--r-- 1 root root  37M Mar 20 10:15 java_pid18881.domOut.index
-rw------- 1 root root 1.3G Mar 20 10:01 java_pid18881.hprof
-rw-r--r-- 1 root root 295K Mar 20 10:16 java_pid18881.i2sv2.index
-rw-r--r-- 1 root root  33M Mar 20 10:15 java_pid18881.idx.index
-rw-r--r-- 1 root root  50M Mar 20 10:15 java_pid18881.inbound.index
-rw-r--r-- 1 root root 7.2M Mar 20 10:15 java_pid18881.index
-rw-r--r-- 1 root root 100K Mar 20 10:15 java_pid18881_Leak_Suspects.zip
-rw-r--r-- 1 root root  12M Mar 20 10:15 java_pid18881.o2c.index
-rw-r--r-- 1 root root  33M Mar 20 10:15 java_pid18881.o2hprof.index
-rw-r--r-- 1 root root  28M Mar 20 10:15 java_pid18881.o2ret.index
-rw-r--r-- 1 root root  49M Mar 20 10:15 java_pid18881.outbound.index
-rw-r--r-- 1 root root  82K Mar 20 10:15 java_pid18881_System_Overview.zip
-rw-r--r-- 1 root root 356K Mar 20 10:15 java_pid18881.threads
-rw-r--r-- 1 root root 256K Mar 20 10:16 java_pid18881_Top_Components.zip

一共会有这么些东西,你只要关注3个*.zip包就行了。
把这3个下载到本地,里面是html文件,打开就是结果。
主要关注 : java_pid18881_Leak_Suspects.zip 这个文件
打开结果:
在这里插入图片描述

我们看到有一个216M的大对象出现了。

然后点击链接进去看是那个线程造成的.
在这里插入图片描述
我们这里就找到了具体业务触发的方法了。

我这里就不贴具体的方法了,最根本的原因是一个图片压缩的功能造成的

Thumbnails.of(file.getInputStream()).scale(0.1f).toFile(outputImg);

如果客户端上传的图片太大,会通过这个方法进行压缩。由于对象本身会很大的话,很容易触发Full GC。

然后我根据这个时间点去监控系统中查询该URL的方法日志的时候,也发现了一个超过9秒的请求,根据方法执行的时间链路基本上也就确定了就是上述代码造成的。

好了,具体过程就是这样。

总结一下 :

  1. 通过开启JVM的参数,在full GC前保留一份hprof文件。
  2. 通过MAT的linux工具直接在服务器上分析,避免文件过大下载下来太慢。
  3. 然后查看结果页来找到具体的大对象

如果你还有什么更好的排查思路以及工具欢迎交流。

这篇关于纯干货:大对象导致FullGC频繁的原因及实践思路的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/665238

相关文章

在 Spring Boot 中实现异常处理最佳实践

《在SpringBoot中实现异常处理最佳实践》本文介绍如何在SpringBoot中实现异常处理,涵盖核心概念、实现方法、与先前查询的集成、性能分析、常见问题和最佳实践,感兴趣的朋友一起看看吧... 目录一、Spring Boot 异常处理的背景与核心概念1.1 为什么需要异常处理?1.2 Spring B

Java对象转换的实现方式汇总

《Java对象转换的实现方式汇总》:本文主要介绍Java对象转换的多种实现方式,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录Java对象转换的多种实现方式1. 手动映射(Manual Mapping)2. Builder模式3. 工具类辅助映

Spring Boot 整合 SSE的高级实践(Server-Sent Events)

《SpringBoot整合SSE的高级实践(Server-SentEvents)》SSE(Server-SentEvents)是一种基于HTTP协议的单向通信机制,允许服务器向浏览器持续发送实... 目录1、简述2、Spring Boot 中的SSE实现2.1 添加依赖2.2 实现后端接口2.3 配置超时时

Python使用getopt处理命令行参数示例解析(最佳实践)

《Python使用getopt处理命令行参数示例解析(最佳实践)》getopt模块是Python标准库中一个简单但强大的命令行参数处理工具,它特别适合那些需要快速实现基本命令行参数解析的场景,或者需要... 目录为什么需要处理命令行参数?getopt模块基础实际应用示例与其他参数处理方式的比较常见问http

Java程序进程起来了但是不打印日志的原因分析

《Java程序进程起来了但是不打印日志的原因分析》:本文主要介绍Java程序进程起来了但是不打印日志的原因分析,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Java程序进程起来了但是不打印日志的原因1、日志配置问题2、日志文件权限问题3、日志文件路径问题4、程序

Java Optional的使用技巧与最佳实践

《JavaOptional的使用技巧与最佳实践》在Java中,Optional是用于优雅处理null的容器类,其核心目标是显式提醒开发者处理空值场景,避免NullPointerExce... 目录一、Optional 的核心用途二、使用技巧与最佳实践三、常见误区与反模式四、替代方案与扩展五、总结在 Java

Spring Boot循环依赖原理、解决方案与最佳实践(全解析)

《SpringBoot循环依赖原理、解决方案与最佳实践(全解析)》循环依赖指两个或多个Bean相互直接或间接引用,形成闭环依赖关系,:本文主要介绍SpringBoot循环依赖原理、解决方案与最... 目录一、循环依赖的本质与危害1.1 什么是循环依赖?1.2 核心危害二、Spring的三级缓存机制2.1 三

Python运行中频繁出现Restart提示的解决办法

《Python运行中频繁出现Restart提示的解决办法》在编程的世界里,遇到各种奇怪的问题是家常便饭,但是,当你的Python程序在运行过程中频繁出现“Restart”提示时,这可能不仅仅是令人头疼... 目录问题描述代码示例无限循环递归调用内存泄漏解决方案1. 检查代码逻辑无限循环递归调用内存泄漏2.

Python中判断对象是否为空的方法

《Python中判断对象是否为空的方法》在Python开发中,判断对象是否为“空”是高频操作,但看似简单的需求却暗藏玄机,从None到空容器,从零值到自定义对象的“假值”状态,不同场景下的“空”需要精... 目录一、python中的“空”值体系二、精准判定方法对比三、常见误区解析四、进阶处理技巧五、性能优化

Python 中的 with open文件操作的最佳实践

《Python中的withopen文件操作的最佳实践》在Python中,withopen()提供了一个简洁而安全的方式来处理文件操作,它不仅能确保文件在操作完成后自动关闭,还能处理文件操作中的异... 目录什么是 with open()?为什么使用 with open()?使用 with open() 进行