记一次JProfiler生产实战内存分析

1,966 阅读2分钟

背景

线上服务器4核cpu突然打满,通过top命令分析后,找到cpu最高的应用,是一个java应用。 找到java应用最高cpu的线程,这里有一个脚本可以直接找到。

#!/bin/bash

if [ $# -ne 2 ]; then
    echo "Usage: $0 <Java_PID> <Number of Threads>"
    exit 1
fi

java_pid=$1
n=$2
user=$(ps -o user= -p $java_pid | awk '{print $1}')
# 1. 获取进程的jstack线程堆栈
jstack_output=$(sudo -u $user jstack -l $java_pid)

# 2. 找出进程下CPU使用率最高的前N个线程的ID(16进制表示)

top_threads_info=$(top -b -n 1 -H -p $java_pid | awk 'NR>7 && $9 != "0.0" {print $1, $9}' | head -n "$n" )

# 3. 在jstack输出中匹配"nid={16进制线程id}",并输出匹配的堆栈
echo "Top $n CPU-consuming threads for Java PID $java_pid (in hexadecimal):"
while read -r thread_id cpu_usage; do
  hex_thread_id=$(printf "0x%x" "$thread_id")
  echo "Thread ID: $thread_id($hex_thread_id), CPU Usage: $cpu_usage%"
  echo ""
  awk -v hex_id="$hex_thread_id" '
         BEGIN { flag=0 ;start=0 }
         { lines[NR] = $0 }
         $0 ~ "nid=" hex_id { print; flag=1;start=NR;print start }
         flag && /^"/ && start!=NR { if (flag) exit }
         END { for (i=start; i<=NR; i++) if (flag && lines[i] !~ /^"/) print lines[i] }
       ' <<< "$jstack_output"
  echo "---------------------------------------------"
done <<< "$top_threads_info"

发现是jvm线程,猜测应该是jvm垃圾回收导致。

调用

jstat -gcutil 1000

查看gc次数,发现full gc 2s一次,基本可以判断是垃圾回收内存导致。

dump下堆内存

jmap -dump:format=b,file=test.hprof

dump下来的文件通过JProfiler进行内存分析

分析过程

打开dump文件,如下 image.png默认是按照实例数来排序的,从图上可以看出,基本是DeviceItemsRowData这个类太多导致。 找到对应的业务代码

点击DeviceItemsRowData,右键使用选定对象 image.png 选择传入引用 image.png 点击在图表中展示 image.png 点击+ image.png 查看到线程号 image.png 点击线程转储,选中查询到的线程号 image.png 点击对应的线程号,找到相关的业务代码 image.png 进行代码分析 image.png 这个业务代码是根据选定时间去查询数据,可以判断是由于选取的时间段过长,返回数据量过多导致。

现在有个问题,能否查看到当时的访问参数?

根据堆栈信息找到controller入口, image.png 可以看到是一个post请求,那我们只要获取到这个请求的入参对象即可,因为该方法没有结束,入参对象肯定是没有垃圾回收的,那就说明还在堆中,只要找到这个对象即可。 点击根据名称排序,找到该对象 image.png 右键->使用选中对象->传出引用 image.png 点击确认后,选中一个对象,即可查看参数 image.png 原来是查询了一年的数据,联系产品及开发,看看如何修复代码。