这里我们不讲JVM的内存划分,垃圾判定算法,垃圾回收算法,垃圾收集器等知识。主要讲的是实际调优的操作,对JVM调优感兴趣的可以看下去。至于垃圾回收算法,可以看看我这篇文章:
“垃圾回收算法
公司系统出现内存溢出的故障,下面是内存溢出排除过程,我采用伪代码模拟了生产环境。
代码语言:javascript复制public class MemoryLeakService {
public List<User> distinct() throws InterruptedException {
List<User> list = new ArrayList<>();
CountDownLatch downLatch = new CountDownLatch(1);
new Thread(() -> {
try {
for (int j = 0; j < 100; j ) {
for (int i = 0; i < 100000; i ) {
User user = new User(String.valueOf(i));
if (!list.contains(user)) {
list.add(user);
}
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
downLatch.countDown();
}
}).start();
downLatch.await();
return list;
}
public static void main(String[] args) throws InterruptedException {
System.out.println(Thread.currentThread());
new Thread().start();
new MemoryLeakService().distinct();
}
}
这里主要是模拟一个缓存加载的过程,将用户数据加载进List
集合中。为了体现效果,我们将堆内存调小,并将内存溢出的堆栈信息打印出来,具体指令如下:
-Xmx8m -XX: HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/temp/20200824.hprof
当运行系统时,出现如下报错
我们发现Thread-1
的堆空间内存溢出了,并且生成如下文件20200824.hprof
。
打开内存分析工具 Eclipse Memory Analyzer
加载20200824.hprof
文件,找到有问题的堆栈信息
控制台打印显示Thread-1由内存溢出,我们进Thread-1看
点击箭头处按钮
选择线程明细
我们发现MemoryLeakService
第24行代码有问题,看看第24行代码
是这个对象导致的。
仔细看代码,发现User对象存入了List集合中
代码语言:javascript复制if (!list.contains(user)) {
list.add(user);
}
看看contains()
源码
发现这里是比较的地址值,那!list.contains(user)
永远为true。这里就相当于List
存了100000 * 100 = 10000000个User
。
我们需要重写User
的equals
方法
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
User user = (User) o;
return Objects.equals(id, user.id);
}
@Override
public int hashCode() {
return Objects.hash(id, name, age);
}
再次运行,没有出现内存溢出了。
好了,上面就是一次简单的内存溢出查找的过程了,关于工具Eclipse Memory Analyzer的使用,自己可以去网上下载下来,练习使用下。说不定哪天你们的系统真的出现内存溢出,自己就有用武之地了。