记一次 Java 应用内存泄漏的定位过程

问题现象

最近,笔者负责测试的某个算法模块机器出现大量报警,报警表现为机器CPU持续高占用。该算法模块是一个优化算法,本身就是CPU密集型应用,一开始怀疑可能是算法在正常运算,但很快这种猜测就被推翻:同算法同学确认后,该算法应用只使用了一个核心,而报警时,一个算法进程占用了服务机器的全部8个核心,这显然不是正常计算造成的。

定位步骤

首先按照CPU问题的定位思路进行定位,对 Java 调用堆栈进行分析:

1、使用top -c 查看 CPU 占用高的进程:

,从 top 命令的结果看,19272 号进程 CPU 占用率最高,基本确定问题是该进程引起,可以从 Command 栏看到这正是算法模块程序,注意图是线下4C机器上复现时的截图

2、使用 ps -mp pid -o THREAD,tid,time命令定位问题线程。
 

ps -mp 19272 -o THREAD,tid,time
USER     %CPU PRI SCNT WCHAN  USER SYSTEM   TID     TIME
USER    191   -    - -         -      -     - 00:36:54
USER    0.0  19    - futex_    -      - 19272 00:00:00
USER   68.8  19    - futex_    -      - 19273 00:13:18
USER   30.2  19    - -         -      - 19274 00:05:50
USER   30.2  19    - -         -      - 19275 00:05:50
USER   30.2  19    - -         -      - 19276 00:05:50
USER   30.1  19    - -         -      - 19277 00:05:49
USER    0.4  19    - futex_    -      - 19278 00:00:05
USER    0.0  19    - futex_    -      - 19279 00:00:00
USER    0.0  19    - futex_    -      - 19280 00:00:00
USER    0.0  19    - futex_    -      - 19281 00:00:00
USER    0.4  19    - futex_    -      - 19282 00:00:04
USER    0.3  19    - futex_    -      - 19283 00:00:03
USER    0.0  19    - futex_    -      - 19284 00:00:00
USER    0.0  19    - futex_    -      - 19285 00:00:00
USER    0.0  19    - futex_    -      - 19286 00:00:00
USER    0.0  19    - skb_wa    -      - 19362 00:00:00

从结果可以看到,出现问题的线程主要是 19273-19277。

3、使用jstack查看出现问题的线程堆栈信息。

由于 jstack 使用的线程号是十六进制,因此需要先把线程号从十进制转换为十六进制。

$ printf "%x\n" 19273
4b49
$ jstack 12262 |grep -A 15 4b49
"main" #1 prio=5 os_prio=0 tid=0x00007f98c404c000 nid=0x4b49 runnable [0x00007f98cbc58000]
java.lang.Thread.State: RUNNABLEat java.util.ArrayList.iterator(ArrayList.java:840)at optional.score.MultiSkuDcAssignmentEasyScoreCalculator.updateSolution(MultiSkuDcAssignmentEasyScoreCalculator.java:794)at optional.score.MultiSkuDcAssignmentEasyScoreCalculator.calculateScore(MultiSkuDcAssignmentEasyScoreCalculator.java:80)at optional.score.MultiSkuDcAssignmentEasyScoreCalculator.calculateScore(MultiSkuDcAssignmentEasyScoreCalculator.java:17)at org.optaplanner.core.impl.score.director.easy.EasyScoreDirector.calculateScore(EasyScoreDirector.java:60)at org.optaplanner.core.impl.score.director.AbstractScoreDirector.doAndProcessMove(AbstractScoreDirector.java:188)at org.optaplanner.core.impl.localsearch.decider.LocalSearchDecider.doMove(LocalSearchDecider.java:132)at org.optaplanner.core.impl.localsearch.decider.LocalSearchDecider.decideNextStep(LocalSearchDecider.java:116)at org.optaplanner.core.impl.localsearch.DefaultLocalSearchPhase.solve(DefaultLocalSearchPhase.java:70)at org.optaplanner.core.impl.solver.AbstractSolver.runPhases(AbstractSolver.java:88)at org.optaplanner.core.impl.solver.DefaultSolver.solve(DefaultSolver.java:191)at app.DistributionCenterAssignmentApp.main(DistributionCenterAssignmentApp.java:61)"VM Thread" os_prio=0 tid=0x00007f98c419d000 nid=0x4b4e runnable"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00007f98c405e800 nid=0x4b4a runnable"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00007f98c4060800 nid=0x4b4b runnable"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x00007f98c4062800 nid=0x4b4c runnable"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x00007f98c4064000 nid=0x4b4d runnable"VM Periodic Task Thread" os_prio=0 tid=0x00007f98c4240800 nid=0x4b56 waiting on condition

可以看到,除了 0x4b49 线程是正常工作线程,其它都是 gc 线程。

此时怀疑:是频繁 GC 导致的 CPU 被占满。

我们可以使用 jstat 命令查看 GC 统计:

$ jstat -gcutil 19272 2000 10
S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT
0.00   0.00  22.71 100.00  97.16  91.53   2122   19.406   282  809.282  828.688
0.00   0.00 100.00 100.00  97.16  91.53   2122   19.406   283  809.282  828.688
0.00   0.00  92.46 100.00  97.16  91.53   2122   19.406   283  812.730  832.135
0.00   0.00 100.00 100.00  97.16  91.53   2122   19.406   284  812.730  832.135
0.00   0.00 100.00 100.00  97.16  91.53   2122   19.406   285  815.965  835.371
0.00   0.00 100.00 100.00  97.16  91.53   2122   19.406   285  815.965  835.371
0.00   0.00 100.00 100.00  97.16  91.53   2122   19.406   286  819.492  838.898
0.00   0.00 100.00 100.00  97.16  91.53   2122   19.406   286  819.492  838.898
0.00   0.00 100.00 100.00  97.16  91.53   2122   19.406   287  822.751  842.157
0.00   0.00  30.78 100.00  97.16  91.53   2122   19.406   287  825.835  845.240

重点关注一下几列:
YGC:年轻代垃圾回收次数
YGCT:年轻代垃圾回收消耗时间
FGC:老年代垃圾回收次数
FGCT:老年代垃圾回收消耗时间
GCT:垃圾回收消耗总时间
可以看到,20s 的时间中进行了 5 次 full GC,仅仅耗费在 GC 的时间已经到了 17s。

  1、增加启动参数,展示详细 GC 过程。通过增加 jvm 参数,更快暴露 GC 问题,并展示 GC 详细过程java -Xmx1024m -verbose:gc

[Full GC (Ergonomics)  1046527K->705881K(1047552K), 1.8974837 secs]
[Full GC (Ergonomics)  1046527K->706191K(1047552K), 2.5837756 secs]
[Full GC (Ergonomics)  1046527K->706506K(1047552K), 2.6142270 secs]
[Full GC (Ergonomics)  1046527K->706821K(1047552K), 1.9044987 secs]
[Full GC (Ergonomics)  1046527K->707130K(1047552K), 2.0856625 secs]
[Full GC (Ergonomics)  1046527K->707440K(1047552K), 2.6273944 secs]
[Full GC (Ergonomics)  1046527K->707755K(1047552K), 2.5668877 secs]
[Full GC (Ergonomics)  1046527K->708068K(1047552K), 2.6924427 secs]
[Full GC (Ergonomics)  1046527K->708384K(1047552K), 3.1084132 secs]
[Full GC (Ergonomics)  1046527K->708693K(1047552K), 1.9424100 secs]
[Full GC (Ergonomics)  1046527K->709007K(1047552K), 1.9996261 secs]
[Full GC (Ergonomics)  1046527K->709314K(1047552K), 2.4190958 secs]
[Full GC (Ergonomics)  1046527K->709628K(1047552K), 2.8139132 secs]
[Full GC (Ergonomics)  1046527K->709945K(1047552K), 3.0484079 secs]
[Full GC (Ergonomics)  1046527K->710258K(1047552K), 2.6983539 secs]
[Full GC (Ergonomics)  1046527K->710571K(1047552K), 2.1663274 secs]

至此基本可以确定,CPU 高负载的根本原因是内存不足导致频繁 GC。

如何进行定位

以上介绍了一些常见的内存泄漏场景,在实际的问题中还需要针对具体的代码进行确定排查。下面结合之前的频繁 GC 问题,讲解一下定位的思路,以及相关工具的使用方法。

线上定位

对于线上服务,如果不能开启 Debug 模式,那么可用的工具较少。推荐方式:
使用 top -c 命令查询 Java 高内存占用程序的进程 pid。然后使用 jcmd 命令获取进程中对象的计数、内存占用信息。

$ jcmd 24600 GC.class_histogram |head -n 10
24600:num     #instances         #bytes  class name
----------------------------------------------1:       2865351      103154208  [J2:       1432655       45844960  org.optaplanner.core.impl.localsearch.scope.LocalSearchMoveScope3:       1432658       34383792  org.optaplanner.core.api.score.buildin.bendablelong.BendableLongScore4:       1193860       28652640  org.optaplanner.core.impl.heuristic.selector.move.generic.ChangeMove5:        241961       11986056  [Ljava.lang.Object;6:        239984        5759616  java.util.ArrayList

结果中,#instances 为对象数量,#bytes 为占用内存大小,单位是 byte,class name 为对应的类名。
排名第一的是 Java 原生类型,实际上是 long 类型。

另外,要注意的是结果中的类可能存在包含关系,例如一个类中含有多个 long 类型数据,那 long 对应的计数也会增加,所以我们要排除一些基本类型,它们可能是我们程序中使用导致的计数增加,重点关注我们程序中的类。

如果仅仅有 jcmd 的结果,其实很难直接找到问题的根本原因。如果问题不能在线下复现,我们基本上只能针对计数较多的类名跟踪变量的数据流,重点关注 new 对象附近的代码逻辑。观察代码逻辑时,重点考虑上述几种常见内存泄漏场景。

线下定位

如果内存泄漏问题可以在线下复现,那么问题定位的工具就比较丰富了。下面主要推荐的两种工具,VisualVM & IDEA。

这里主要讲一下IDEA调试定位思路:

使用 IDEA 调试器定位内存泄漏问题

如果以上过程依然不能有效的分析出问题的根本原因,还可以使用 IDEA 的调试功能进行定位。
配置好程序的运行参数,正常复现问题之后,对程序打断点并逐步追踪。

重点关注的是程序需要大量运行时间的代码部分,我们可以使用调试暂停功能获得一个内存快照。
然后在此运行并暂停,这时候在调试的 Memory 视图中可以看到哪些类在快速增加。基本上可以断定问题的原因是两次运行中 new 该对象的语句。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/827917.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

springboot基于点餐码 二维码在线点餐系统vue.js+java

Maven: 项目管理和构建自动化工具,用于java项目。 java: 广泛使用的编程语言,适用于构建跨平台应用。 Springmvc:从而在使用Spring进行WEB开发时,可以选择使用Spring的Spring MVC框架。 MyBatis: java持久层框架,支持定制化SQL、存…

第68天:APP攻防-XposedFridaHook证书校验反代理代理转发

目录 思维导图 案例一:某牛防抓包-xposed&frida&r0capture 如何检测是否启动了反代理 xp框架 方案二:某社交防抓包-Proxifier&frida&r0capture 思维导图 案例一:某牛防抓包-xposed&frida&r0capture 这里某牛软…

Python | Leetcode Python题解之第46题全排列

题目: 题解: class Solution:def permute(self, nums):""":type nums: List[int]:rtype: List[List[int]]"""def backtrack(first 0):# 所有数都填完了if first n: res.append(nums[:])for i in range(first, n):# 动…

WebSocket的原理、作用、API、常见注解和生命周期的简单介绍,附带SpringBoot示例

文章目录 原理作用客户端 API服务端 API生命周期常见注解SpringBoot示例 WebSocket是一种 通信协议 ,它在 客户端和服务器之间建立了一个双向通信的网络连接 。WebSocket是一种基于TCP连接上进行 全双工通信 的 协议 。 WebSocket允许客户端和服务器在 单个TCP连接上…

基于FPGA轻松玩转AI

启动人工智能应用从来没有像现在这样容易!受益于像Xilinx Zynq UltraScale MPSoC 这样的FPGA,AI现在也可以离线使用或在边缘部署、使用.可用于开发和部署用于实时推理的机器学习应用,因此将AI集成到应用中变得轻而易举。图像检测或分类、模式…

Python写个二维码

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、进入官网下载二、下载一下三.输入代码 前言 提示:以下是本篇文章正文内容,下面案例可供参考 一、进入官网下载 官网 pip insta…

vue3推荐算法

Vue 3 推荐算法主要指的是在 Vue 3 框架中实现的或者适用于 Vue 3 的算法库或组件库。Vue 3 由于其优秀的设计和性能,被广泛应用于构建各种类型的应用程序,包括需要复杂算法支持的项目。以下是一些在 Vue 3 中可能会用到的推荐算法资源: Vue-…

田忌赛马【洛谷P1650】

P1650 田忌赛马 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) #include<iostream> #include <algorithm> #include<cstdio> #include <map> using namespace std; const int N1e5100; int n; map<int,int>a,b;//映射&#xff0c;速度->数量…

新网站上线需要注意什么?

质量保证&#xff1a;确保网站的所有功能和页面都经过了充分的测试&#xff0c;并且在各种不同的浏览器和设备上都能够正常运行。检查所有链接、表单和交互式元素&#xff0c;确保它们都能够按照预期工作。优化性能&#xff1a;确保网站加载速度快&#xff0c;响应迅速。优化图…

Python-VBA函数之旅-isinstance函数

目录 一、isinstance函数的常见应用场景&#xff1a; 二、isinstance函数使用注意事项&#xff1a; 三、如何用好isinstance函数&#xff1f; 1、isinstance函数&#xff1a; 1-1、Python&#xff1a; 1-2、VBA&#xff1a; 2、推荐阅读&#xff1a; 个人主页&#xff…

基于spring boot学生综合测评系统

基于spring boot学生综合测评系统设计与实现 开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09; 数据库工具&#xff1a;Navicat11 开发软件…

typedef 定义函数指针

typdef int(*FUNC_TYPE)(int,int) FUNC_TYPE p NULL; 定义了一个函数指针 函数指针作为函数的参数的用法demon

黄金行情下跌有投资机会吗?

尽管黄金价格的波动常常引起投资者的高度关注&#xff0c;但行情的下跌未必只是警讯&#xff0c;亦可能蕴藏着某些难得的投资机会。总之&#xff0c;答案是肯定的——在黄金行情下跌时&#xff0c;依旧有适宜的投资机会&#xff0c;只是这需要投资者具备相应的应对知识和策略。…

美森快船和以星快船有什么区别?美线海运都有哪些快船?

在繁忙的国际海运市场中&#xff0c;快船服务以其高效、快捷的特点受到广大货主的青睐。其中&#xff0c;美森快船和以星快船作为知名的海运服务提供商&#xff0c;凭借着卓越的服务品质&#xff0c;在航运界树立了良好的口碑。那么&#xff0c;美森快船和以星快船究竟有何不同…

利用ollama和open-webui本地部署通义千问Qwen1.5-7B-Chat模型

目录 1 安装ollama 2 安装open-webui 2.1 镜像下载 3 配置ollama的模型转换工具环境 3.1 下载ollama源码 3.2 下载ollama子模块 3.3 创建ollama虚拟环境 3.4 安装依赖 3.5 编译量化工具 7 创建ollama模型 8 运行模型 参考文献&#xff1a; 1 安装ollama curl -fsSL …

2-2 任务:闰年判断

本次课&#xff0c;我们讨论了闰年的判断方法、关系运算符与关系表达式、逻辑运算符与逻辑表达式&#xff0c;以及流程控制结构中的选择结构。 闰年判断 闰年是为了使日历年与地球绕太阳公转的时间保持一致而设定的&#xff0c;具有366天。闰年的判断规则如下&#xff1a; 普…

16V/2A高集成功率MOS同步降压转换器SOT23-6封装

概述&#xff1a; PCD8020 是一款内部集成两个功率 MOS 的高效率 2A 同步整流降压转换器。 该器件提供PWM 与 PFM 两种控制模式&#xff0c; 能够在很宽的负载范围内实现高效率。PCD8020 采用小巧的 SOT23-6 封装&#xff0c; 外围器件少&#xff0c; 从而实现小尺寸的系统电源…

【算法】约瑟夫环

文章目录 题目一1.数组模拟1.1出圈顺序递归求出圈顺序 1.2最后出圈人 2.环形链表【DEMO】3.递推求最后出圈人3.13.2 题目二1.数组模拟2.递推求最后出圈人2.12.2 题目一 1-n编号 s开始1-m报数 报到m出圈 求出圈顺序or最后人 1.数组模拟 1.1出圈顺序 递归求出圈顺序 // AC输入…

python 如何判断两个字典是否相等

Python 字典的 cmp() 函数用于比较两个字典元素。 语法 cmp()方法语法&#xff1a; cmp(dict1, dict2)参数 dict1 -- 比较的字典。 dict2 -- 比较的字典。 返回值 如果两个字典的元素相同返回0&#xff0c;如果字典dict1大于字典dict2返回1&#xff0c;如果字典dict1小于…

为AI电脑生态注入强悍动力,安耐美PlatiGemini 1200W高性能电源

在DIY攒机的过程中&#xff0c;电源是非常重要的一环&#xff0c;现在高性能的硬件功耗往往很高&#xff0c;因此一款优秀的电源整个系统稳定运行的基石。最近&#xff0c;我发现一款由安耐美&#xff08;Enermax&#xff09;推出的PlatiGemini 1200W电源&#xff0c;它不仅满足…