【Java系列】OOM 时,JVM 堆栈信息保存和分析

一、前言

在日常开发中,即使代码写得再谨慎,免不了还是会发生各种意外的事件,比如服务器内存突然飙高,又或者发生内存溢出(OOM)。当发生这种情况时,我们怎么去排查,怎么去分析原因呢?

一般遇到这种情况,都是需要 dump JVM 堆栈信息来进行排查和分析。

二、JVM 堆栈信息保存方式

这里介绍两种 dump JVM 堆栈信息的方式,一种是被动的(自动保存),一种是主动的(手动保存)。

2.1 自动保存

其实在很多时候我们是不知道何时会发生 OOM,所以需要在发生 OOM 时自动生成 dump 文件。怎么做?

很简单,JVM 增加如下参数即可:

-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/jvm/heapdump.hprof

附上笔者完整的 JVM 参数(JDK 1.8):

-XX:MetaspaceSize=256m
-XX:MaxMetaspaceSize=512m
-Xms2g
-Xmx2g
-Xss256k
-XX:+UseG1GC
-XX:MaxGCPauseMillis=50
-XX:+UnlockExperimentalVMOptions
-XX:+UseCGroupMemoryLimitForHeap
-XX:+HeapDumpOnOutOfMemoryError 
-XX:HeapDumpPath=/var/log/jvm/heapdump.hprof
参数描述
-XX:MetaspaceSize=256m设置元空间(Metaspace)的初始大小为256MB。元空间用于存储类和方法信息等数据
-XX:MaxMetaspaceSize=512m设置元空间的最大大小为512MB。如果元空间需要更多空间,JVM 将动态扩展元空间的大小,但不会超过这个最大值
-Xms2g设置Java虚拟机的初始堆大小为2GB。这是堆的初始可用空间
-Xmx2g设置Java虚拟机的最大堆大小为2GB。这是堆的最大可用空间
-Xss256k设置每个线程的栈大小为256KB。栈大小影响可以创建的线程数量,太小可能会导致 StackOverflowError,太大则会消耗更多内存
-XX:+UseG1GC启用G1垃圾收集器。G1是Java 7及之后版本引入的一种垃圾收集器,目标是取代CMS收集器,提供更可控的垃圾收集性能
-XX:MaxGCPauseMillis=50设置最大垃圾收集停顿时间为50毫秒。G1垃圾收集器会尽量控制垃圾收集停顿时间,这个参数可以用来指定一个最大值
-XX:+UnlockExperimentalVMOptions启用实验性的虚拟机选项。这个选项用于解锁实验性的JVM参数,但这些参数可能在未来的版本中发生变化或移除
-XX:+UseCGroupMemoryLimitForHeap使用控制组(cgroup)内存限制作为堆的最大大小。这个选项用于容器化环境中,让JVM可以识别并遵守容器的内存限制
-XX:+HeapDumpOnOutOfMemoryError在发生OOM时生成堆转储文件。这个参数用于配置JVM在发生内存溢出时生成堆转储文件,以便进行内存分析
-XX:HeapDumpPath这个参数用于指定生成的堆转储文件的保存路径

因笔者的服务部署在 K8S 环境中,为了更好的获取到自动生成的堆栈文件,需要将容器内保存堆栈信息的路径挂载到宿主机上,以下是 K8S deployment.yaml 相关示例:

apiVersion: apps/v1
kind: Deployment
metadata:
  ...
spec:
  template:
    spec:
      imagePullSecrets:
        - name: harbor
      volumes:
        - name: dump
          hostPath:
            path: /var/log/jvm
            type: DirectoryOrCreate
      containers:
        - name: {{ .Chart.Name }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          volumeMounts:
            - name: dump
              mountPath: /var/log/jvm
  ...

2.2 手动保存

保存当前的 堆信息 到 heapdump.hprof 文件中(保存在当前路径下):

// format=b:dump文件只支持二进制格式
jmap -dump:format=b,file=heapdump.hprof PID

三、JVM 堆栈信息分析工具

推荐使用 MAT:Eclipse's Memory Analysis Tool 进行 JVM 堆栈信息分析。由于最新版本 1.14.0 所需的最低 Java 版本是 Java 11,所以我们下载 支持 Java 8 的旧版本:

Memory Analyzer 1.8.0 Release

1. 左上角 File --> Open Heap Dump --> Leak  Suspects Report(泄漏可疑报告)

2. Problem Suspect --> See stacktrace,可看到堆溢出时候的堆栈日志:

即可定位到出现问题的代码行! 

3. OOM 示例代码

package com.sensetime.idea.aurora.task;

import java.util.ArrayList;
import java.util.List;

/**
 * <p>desc</p>
 *
 * @author Hyatt
 * @date 2024/3/13
 */
public class MainTest {

    public static void main(String[] args) {
        try {
            List<Integer> list = new ArrayList<>();
            while (true) {
                list.add(123124123);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

输出:

java.lang.OutOfMemoryError: GC overhead limit exceeded
Dumping heap to D:\tmp\heapdump.hprof ...
Heap dump file created [9754557 bytes in 0.074 secs]
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
	at java.lang.Integer.valueOf(Integer.java:832)
	at com.sensetime.idea.aurora.task.MainTest.main(MainTest.java:18)

run MainTest#main 方法时,指定 vm options:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hyatt1024

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值