性能调优-内存泄露的排查方法

image.png

背景

内存泄露的定义

内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

思路

  • top命令查看服务器负载

    1
    top
  • jps查看java进程pid

    1
    jps -l
  • jstack查看运行栈信息

    1
    jstack -l <pid>

    利用jstack -l 查看那些cpu使用率过高的线程,看是否大多数是gc线程,如果是说明gc过于频繁,而且耗时过长,导致应用线程被挂起,无法响应客户端发来的请求,这种情况就应该是内存泄露的问题了。

  • jmap 导出快照

    1
    jmap -dump:live,format=b,file=heap.bin 21737
  • jhat分析快照

    1
    jhat -J-Xmx512M heap.bin

    jhat使用方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    Usage:  jhat [-stack <bool>] [-refs <bool>] [-port <port>] [-baseline <file>] [-debug <int>] [-version] [-h|-help] <file>

    -J<flag> Pass <flag> directly to the runtime system. For
    example, -J-mx512m to use a maximum heap size of 512MB
    -stack false: Turn off tracking object allocation call stack.
    -refs false: Turn off tracking of references to objects
    -port <port>: Set the port for the HTTP server. Defaults to 7000
    -exclude <file>: Specify a file that lists data members that should
    be excluded from the reachableFrom query.
    -baseline <file>: Specify a baseline object dump. Objects in
    both heap dumps with the same ID and same class will
    be marked as not being "new".
    -debug <int>: Set debug level.
    0: No debug output
    1: Debug hprof file parsing
    2: Debug hprof file parsing, no server
    -version Report version number
    -h|-help Print this help and exit
    <file> The file to read

    For a dump file that contains multiple heap dumps,
    you may specify which dump in the file
    by appending "#<number>" to the file name, i.e. "foo.hprof#3".
  • 查看Html文件分析内存内容

JFR采集数据

1.开启JFR对VM运行时信息转存

#开启JFR
-XX:+FlightRecorder
#设置延时和输出文件
-XX:StartFlightRecording=duration=3s,filename=flight.jfr

1
2
3
4
#编译
javac HashCodeMemoryLeak.java
#运行
java -Xms5m -Xmx5m -XX:+FlightRecorder -XX:StartFlightRecording=duration=2s,filename=flight.jfr HashCodeMemoryLeak

2.通过JMC打开记录文件
image.png
3.分析泄露原因,一般都是那种大对象

案例

内存泄露简单案例

1.因hashcode不一致引起的内存泄露问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import java.util.HashSet;
import java.util.Set;

class Key {
private String key;
//未重写hashcode其返回对应的是对象的堆地址每个对象都不一样
public Key(String key) {
this.key = key;
}
// 去掉下面注释即可解决内存泄露的问题
// @Override
// public boolean equals(Object o) {
// if (this == o) return true;
// if (o == null || getClass() != o.getClass()) return false;
// Key key1 = (Key) o;
// return Objects.equals(key, key1.key);
// }
//
// @Override
// public int hashCode() {
// return Objects.hash(key);
// }
}

/**
* 因哈希引起的内存泄露导致内存溢出
*
* 初始化堆大小:-Xms5m -Xmx5m
*/
public class HashCodeMemoryLeak {

public static final int max = 10000;

private static Set<Key> set = new HashSet<>(2<<4);

public static void main(String[] args) {
int idx = 0;
while (true){
set.add(new Key(String.valueOf(idx)));
idx++;
if (idx==max){//达到最大值释放内存(其实无法释放因为hashcode不一样)
while (idx>0) {
set.remove(new Key(String.valueOf(idx)));
idx--;
}
System.out.println(String.format("set size : %s free memory :%s M" ,set.size(), getFreeMemory()));
}
}
}
/**
* 获取堆区内存
* @return
*/
public static long getFreeMemory() {
return Runtime.getRuntime().freeMemory() / (1024 * 1024);
}
}

2.在JDK的ArrayList中remove方法

1
2
3
4
5
6
7
8
9
10
public E remove(int index) {

rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)System.arraycopy(elementData, index+1, elementData, index,numMoved);
elementData[--size] = null; // clear to let GC do its work(消除对象的引用,gc时可以将其收集)
return oldValue;
}

3.threadlocal类弱引用类型引起的内存泄露问题

资料

相关工具:https://mikeygithub.github.io/2021/07/22/yuque/rdphy6/


性能调优-内存泄露的排查方法
https://mikeygithub.github.io/2022/09/01/yuque/性能调优-内存泄露的排查方法/
作者
Mikey
发布于
2022年9月1日
许可协议