记一次 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: RUNNABLE
    at 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  [J
   2:       1432655       45844960  org.optaplanner.core.impl.localsearch.scope.LocalSearchMoveScope
   3:       1432658       34383792  org.optaplanner.core.api.score.buildin.bendablelong.BendableLongScore
   4:       1193860       28652640  org.optaplanner.core.impl.heuristic.selector.move.generic.ChangeMove
   5:        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.mfbz.cn/a/559618.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何使用云数据库GaussDB管理平台进行实例安装?

前言 随着数字经济的蓬勃发展,数据库也成为企业的关键技术生产力,也是各行各业数字化转型的必要根基。GaussDB作为新一代分布式数据库,核心代码100%自主创新,具备高可用、高安全、高性能、高弹性、高智能、易部署、易迁移的特性&…

Java作业6-Java类的基本概念三

编程1 import java.util.*;abstract class Rodent//抽象类 {public abstract String findFood();//抽象方法public abstract String chewFood(); } class Mouse extends Rodent {public String findFood(){ return "大米"; }public String chewFood(){ return "…

shm 共享内存

shm 共享内存 0,命令1,了解:2,程序: 0,命令 ipcs 查看分配的共享内存ipcrm -m shmid 删掉分配的共享内存1,了解: 1),进程通信的一种 2),地址映射出来后,就不…

C语言数据结构之顺序表

目录 1.线性表2.顺序表2.1顺序表相关概念及结构2.2增删查改等接口的实现 3.数组相关例题 1.线性表 线性表(linear list)是n个具有相同特性(数据类型相同)的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构&#xff…

Github 2024-04-20 开源项目日报 Top10

根据Github Trendings的统计,今日(2024-04-20统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量非开发语言项目2Python项目2Swift项目2HTML项目1CSS项目1Go项目1C项目1C++项目1Rust项目1编程面试大学:成为软件工程师的全面学习计划 创建周期…

半导体材料(三)——P-N结和金属-半导体接触

本篇为西安交通大学本科课程《电气材料基础》的笔记。 本篇为这一单元的第三篇笔记,上一篇传送门。 p-n结和金属-半导体接触 p-n结 无偏压开路状态 如图a所示,左边是n型掺杂,右边是p型掺杂,在n区和p区之间形成了一个不连续的…

WARNING: No swap limit support——查看docker状态时提示警告

环境:Ubuntu 20.04 1、警告详情 执行命令 service docker status如下图 2、解决办法 2.1 修改文件 执行命令 vim /etc/default/grub在GRUB_CMDLINE_LINUX中追加cgroup_enablememory swapaccount1,如下: # If you change this file…

【蓝桥杯嵌入式】蓝桥杯嵌入式第十四届省赛程序真题,真题分析与代码讲解

🎊【蓝桥杯嵌入式】专题正在持续更新中,原理图解析✨,各模块分析✨以及历年真题讲解✨都已更新完毕,欢迎大家前往订阅本专题🎏 🎏【蓝桥杯嵌入式】蓝桥杯第十届省赛真题 🎏【蓝桥杯嵌入式】蓝桥…

攻防世界18.fileclude

18.fileclude include函数:包含并执行变量或者文件。 if:是if语句用来判断。 isset:判断变量是否存在,值是否为NULL。 $_GET:接收表单提交数据,并把数据附加到url链接当中。 逻辑运算符&&&#xff…

【提示学习论文】BlackVIP: Black-Box Visual Prompting for Robust Transfer Learning论文原理

BlackVIP: Black-Box Visual Prompting for Robust Transfer Learning BlackVIP:稳健迁移学习的黑盒视觉提示 问题 黑盒白盒? 黑盒和白盒的概念与对预训练模型内部参数的了解程度相关。黑盒指的是对预训练模型的参数和结构缺乏详细了解,通常只能通过使…

NAT基本配置

配置IP完成及缺省的路由如下; 此时R1pingISP是ping不通的,因为缺省是可以将数据传给R3,但是R3传不回去,知道目标IP地址但因其是私有内部IP,而自己的是公有IP,所以传不过去,此时就需要R2这个边界…

2024 发布Maven项目到中央仓库

注册sonatype账号 Maven中央仓库并不支持直接发布jar包,sonatype是其指定的第三方仓库之一,发布到此的项目会被定时同步到中央仓库 官方教程地址:https://central.sonatype.org/register/central-portal/ 访问网址:https://centra…

文件操作和IO

1.认识文件 我们先来认识狭义上的⽂件(file)。针对硬盘这种持久化存储的I/O设备,当我们想要进⾏数据保存时,往往不是保存成⼀个整体,⽽是独⽴成⼀个个的单位进⾏保存,这个独⽴的单位就被抽象成⽂件的概念,就类似办公桌…

# 从浅入深 学习 SpringCloud 微服务架构(三)注册中心 Eureka(2)

从浅入深 学习 SpringCloud 微服务架构(三)注册中心 Eureka(2) 段子手168 1、搭建 EurekaServer 注册中心,使用 Eureka 的步骤: 1)搭建 EurekaServer 创建工程,导入依赖坐标&…

Python-VBA函数之旅-globals函数

目录 一、globals函数的常见应用场景: 二、globals函数与locals函数对比分析: 1、globals函数: 1-1、Python: 1-2、VBA: 2、推荐阅读: 个人主页:https://blog.csdn.net/ygb_1024?spm101…

基于springboot+vue+Mysql的广场舞团管理系统

开发语言:Java框架:springbootJDK版本:JDK1.8服务器:tomcat7数据库:mysql 5.7(一定要5.7版本)数据库工具:Navicat11开发软件:eclipse/myeclipse/ideaMaven包:…

牛客小白月赛91

A.Bingbong的化学世界 链接:登录—专业IT笔试面试备考平台_牛客网 来源:牛客网 时间限制:C/C 1秒,其他语言2秒 空间限制:C/C 262144K,其他语言524288K 64bit IO Format: %lld 题目描述 🌙“上…

AndroidStudio右下角显示内存使用情况

目录 一.具体效果 二.4.0以下版本 三.4.0以上版本 四.增加内存配置 一.具体效果 二.4.0以下版本 1.打开 Android Studio. 2.进入设置界面。点击 Android Studio 左上角的“File”,然后选择“Settings” 3.在设置界面中,选择“Appearance & Beha…

关于图像YUV格式分类和排布方式的全学习

【学习笔记】关于图像YUV格式分类和排布方式的全学习_yuv图像-CSDN博客 下图是将多个yuv420p图像(A和B),拼接成一个画面的思路 A大小:416*64 B大小:416*208 将A和B合并到一个416*416的尺寸上,代码如下 //整合char * ptd;ptd (char * ) malloc (416*41…

手把手教你实现 C 语言的函数多参默认值 「下」

以下内容为本人的学习笔记,如需要转载,请声明原文链接 微信公众号「ENG八戒」https://mp.weixin.qq.com/s/ifnDcV7AKrh6eVihVK9l5A 本文上接《手把手教你实现 C 语言的函数多参默认值 上》,下文提及的一些概念来源于上文,为方便阅…
最新文章