纯干货:大对象导致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 Security简介、使用与最佳实践

《SpringSecurity简介、使用与最佳实践》SpringSecurity是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架,本文给大家介绍SpringSec... 目录一、如何理解 Spring Security?—— 核心思想二、如何在 Java 项目中使用?——

java.sql.SQLTransientConnectionException连接超时异常原因及解决方案

《java.sql.SQLTransientConnectionException连接超时异常原因及解决方案》:本文主要介绍java.sql.SQLTransientConnectionExcep... 目录一、引言二、异常信息分析三、可能的原因3.1 连接池配置不合理3.2 数据库负载过高3.3 连接泄漏

javacv依赖太大导致jar包也大的解决办法

《javacv依赖太大导致jar包也大的解决办法》随着项目的复杂度和依赖关系的增加,打包后的JAR包可能会变得很大,:本文主要介绍javacv依赖太大导致jar包也大的解决办法,文中通过代码介绍的... 目录前言1.检查依赖2.更改依赖3.检查副依赖总结 前言最近在写项目时,用到了Javacv里的获取视频

防止Linux rm命令误操作的多场景防护方案与实践

《防止Linuxrm命令误操作的多场景防护方案与实践》在Linux系统中,rm命令是删除文件和目录的高效工具,但一旦误操作,如执行rm-rf/或rm-rf/*,极易导致系统数据灾难,本文针对不同场景... 目录引言理解 rm 命令及误操作风险rm 命令基础常见误操作案例防护方案使用 rm编程 别名及安全删除

C++统计函数执行时间的最佳实践

《C++统计函数执行时间的最佳实践》在软件开发过程中,性能分析是优化程序的重要环节,了解函数的执行时间分布对于识别性能瓶颈至关重要,本文将分享一个C++函数执行时间统计工具,希望对大家有所帮助... 目录前言工具特性核心设计1. 数据结构设计2. 单例模式管理器3. RAII自动计时使用方法基本用法高级用法

PHP应用中处理限流和API节流的最佳实践

《PHP应用中处理限流和API节流的最佳实践》限流和API节流对于确保Web应用程序的可靠性、安全性和可扩展性至关重要,本文将详细介绍PHP应用中处理限流和API节流的最佳实践,下面就来和小编一起学习... 目录限流的重要性在 php 中实施限流的最佳实践使用集中式存储进行状态管理(如 Redis)采用滑动

ShardingProxy读写分离之原理、配置与实践过程

《ShardingProxy读写分离之原理、配置与实践过程》ShardingProxy是ApacheShardingSphere的数据库中间件,通过三层架构实现读写分离,解决高并发场景下数据库性能瓶... 目录一、ShardingProxy技术定位与读写分离核心价值1.1 技术定位1.2 读写分离核心价值二

深入浅出Spring中的@Autowired自动注入的工作原理及实践应用

《深入浅出Spring中的@Autowired自动注入的工作原理及实践应用》在Spring框架的学习旅程中,@Autowired无疑是一个高频出现却又让初学者头疼的注解,它看似简单,却蕴含着Sprin... 目录深入浅出Spring中的@Autowired:自动注入的奥秘什么是依赖注入?@Autowired

MySQL分库分表的实践示例

《MySQL分库分表的实践示例》MySQL分库分表适用于数据量大或并发压力高的场景,核心技术包括水平/垂直分片和分库,需应对分布式事务、跨库查询等挑战,通过中间件和解决方案实现,最佳实践为合理策略、备... 目录一、分库分表的触发条件1.1 数据量阈值1.2 并发压力二、分库分表的核心技术模块2.1 水平分

深度剖析SpringBoot日志性能提升的原因与解决

《深度剖析SpringBoot日志性能提升的原因与解决》日志记录本该是辅助工具,却为何成了性能瓶颈,SpringBoot如何用代码彻底破解日志导致的高延迟问题,感兴趣的小伙伴可以跟随小编一起学习一下... 目录前言第一章:日志性能陷阱的底层原理1.1 日志级别的“双刃剑”效应1.2 同步日志的“吞吐量杀手”