jvm堆/内存溢出问题详细排查步骤

1.堆溢出异常排查详细步骤

​ 在 Java 应用程序中,堆溢出异常(OutOfMemoryError: Java heap space)是最常见的内存问题之一,表示 JVM 的堆内存不足以分配新的对象。排查堆溢出异常需要系统性地分析问题根源,包括内存使用情况、代码逻辑、配置参数等。以下是详细的排查步骤:

1.确认异常信息

1.检查日志 :

异常堆栈

​ 查看完整异常信息,例如:

java.lang.OutOfMemoryError: Java heap space
    at com.example.MyClass.createLargeObject(MyClass.java:50)
    at com.example.Main.main(Main.java:20)

​ 记录触发异常的代码位置(如 MyClass.java:50)。

上下文

​ 检查异常发生的时间点和触发条件(例如某个请求、任务或批量操作)。

2.初步判断 :

堆空间不足:通常是对象分配过多或内存泄漏。

其他 OOM 类型:排除非堆相关的 OOM(如 Metaspace 或 StackOverflow)。

2.检查堆内存配置

1.查看 JVM 参数 :

​ 1.命令:

ps -ef | grep java

​ 或查看启动脚本,确认堆参数:

​ 1.-Xms:初始堆大小。

​ 2.-Xmx:最大堆大小。

​ 3.-Xmn:年轻代大小(可选)。

​ 2.示例

java -Xms512m -Xmx1024m -jar app.jar

​ 初始堆 512MB,最大堆 1GB。

2.分析 :

​ 1.堆大小是否足够

​ 1.如果 -Xmx 设置过小(如 512MB),而应用需要更多内存,需增加。

​ 2.典型场景:批量处理大数据、缓存大量对象。

​ 2.年轻代和老年代比例

​ 如果 -Xmn 过小,可能导致频繁 Minor GC,老年代快速填满。

3.临时调整

​ 1.增加堆大小测试:

java -Xms1g -Xmx2g -jar app.jar

​ 如果问题缓解,说明堆内存不足是直接原因,但需深入分析为何内存需求高。

3.收集堆内存使用数据

1.启用 GC 日志

​ 参数:

java -Xms512m -Xmx1024m -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:gc.log -jar app.jar

​ 2.分析日志(gc.log):

​ 检查 GC 频率和堆使用情况。

​ 示例:

10.123: [Full GC (Ergonomics) [PSYoungGen: 1024K->0K(9216K)] [ParOldGen: 9216K->9216K(9216K)] 10240K->9216K(18432K), 0.0501234 secs]

​ 老年代(ParOldGen)满且未回收,触发 OOM。

2.使用监控工具

​ 1.JVisualVM :

​ 1.JDK 自带工具,连接运行中的 JVM。
2.查看堆使用曲线,观察 Eden、Survivor、Old 区域的增长。

​ 2.JConsole

​ 监控内存、线程、GC。

​ 3.外部工具

​ VisualVM、Mission Control、Prometheus + JMX。

3.生成堆转储(Heap Dump)

​ 1.参数:

java -Xms512m -Xmx1024m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dump.hprof -jar app.jar

​ 2.触发条件

​ OOM 时自动生成 .hprof 文件。

​ 3.手动生成

jmap -dump:live,format=b,file=dump.hprof <pid>

​ :通过 jps 或 ps -ef | grep java 获取。

4.分析堆转储

1.工具

​ 1.Eclipse MAT(Memory Analyzer Tool)

​ 打开 dump.hprof,分析内存占用。

​ 2.JVisualVM

​ 加载堆转储,查看对象分布。

2.分析步骤

​ 1.查看概览

​ MAT 的 “Dominator Tree” 或 “Histogram” 显示占用内存最多的对象。

​ 示例:

Class NameShallow HeapRetained HeapPercentage
java.util.ArrayList2410,485,76050.12%

​ Retained Heap 表示对象及其引用链占用的总内存。

​ 2.查找大对象

​ 检查 java.util.ArrayList、byte[] 等常见大对象。

​ 3.检查引用链

​ 在 MAT 中右键对象 -> “Path to GC Roots”,找出谁持有引用。

​ 示例:某个缓存类(如 HashMap)未释放。

​ 4.泄漏嫌疑(Leak Suspects)

​ MAT 自动生成报告,指出可能的内存泄漏点。

3.常见问题

​ 1.内存泄漏

​ 1.静态集合(如 static List)未清理。

​ 2.缓存未设置过期策略。

​ 2.大对象分配

​ 1.一次性加载大文件到内存。

​ 2.无限增长的集合。

5.分析代码逻辑

1.根据堆转储定位

​ 1.代码位置

​ 从堆栈或引用链找到问题代码(例如 MyClass.createLargeObject)。

​ 2.检查逻辑

​ 1.集合使用:是否无限添加元素到 List、Map?

​ 2.资源释放:文件流、数据库连接是否关闭?

​ 3.缓存管理:是否使用无界缓存(如 HashMap 而非 LinkedHashMap)?

2.示例问题

public class LeakExample {
    private static List<byte[]> data = new ArrayList<>();
    public static void main(String[] args) {
        while (true) {
            data.add(new byte[1024 * 1024]); // 每次分配 1MB
        }
    }
}

问题:data 无界增长,最终耗尽堆。

6.检查 GC 行为

1.GC 日志分析

​ 1.频繁 Full GC

​ 老年代满,频繁 Full GC 但回收少。

​ 2.示例:

[Full GC 9216K->9216K(9216K), 0.05 secs]

​ 老年代无空间释放,可能是内存泄漏。

​ 3.年轻代溢出

​ Eden 区快速填满,对象直接晋升老年代。

2.GC 配置优化

​ 1.调整分代比例

java -Xms1g -Xmx1g -Xmn512m -jar app.jar

​ 2.切换 GC 算法

​ 1.默认 Parallel GC:

-XX:+UseParallelGC

​ 2.G1 GC(低停顿):

-XX:+UseG1GC -XX:MaxGCPauseMillis=200

7. 优化与验证

优化代码

内存泄漏

​ 1.添加清理逻辑:

data.clear(); // 定期清理

​ 2.使用有界缓存:

Map<String, Object> cache = new LinkedHashMap<String, Object>(16, 0.75f, true) {
    @Override
    protected boolean removeEldestEntry(Map.Entry eldest) {
        return size() > 1000; // 限制 1000 条
    }
};

大对象

​ 分片处理:

while (reader.hasNext()) {
    process(reader.nextChunk()); // 分块读取
}

2.内存溢出排查步骤 -OOM

​ 内存溢出(OutOfMemoryError)是 Java 应用程序中常见的异常,可能由堆、方法区、栈等多种原因引发。排查内存溢出需要根据具体类型和场景,系统性地收集数据、分析原因并优化代码或配置。以下是详细的排查步骤,涵盖所有可能的内存溢出情况。

1.确定内存溢出类型

​ 1.检查异常信息

​ 1.查看日志或控制台输出,识别具体异常:

​ 1.java.lang.OutOfMemoryError: Java heap space:堆内存不足。

​ 2.java.lang.OutOfMemoryError: Metaspace(JDK 8+)或 PermGen space(JDK 7-):方法区内存不足。

​ 3.java.lang.StackOverflowError:栈溢出(虽不是严格的 OOM,但常与内存相关)。

​ 4.java.lang.OutOfMemoryError: unable to create new native thread:线程过多,系统内存不足。

​ 5.java.lang.OutOfMemoryError: GC overhead limit exceeded:垃圾回收时间过长,回收效率低。

​ 2.记录堆栈

java.lang.OutOfMemoryError: Java heap space
    at com.example.MyClass.createLargeObject(MyClass.java:50)

​ 3.初步判断

​ 根据异常类型,确定排查方向(堆、方法区、栈等)。

2. 收集基本信息

​ 1.JVM 参数

ps -ef | grep java

​ 1.堆:-Xms(初始)、-Xmx(最大)、-Xmn(年轻代)。

​ 2.方法区:-XX:MetaspaceSize、-XX:MaxMetaspaceSize(JDK 8+)或 -XX:MaxPermSize(JDK 7-)。

​ 3.栈:-Xss(线程栈大小)。

java -Xms512m -Xmx1024m -XX:MaxMetaspaceSize=256m -Xss1m -jar app.jar

​ 2.系统资源

free -m

​ 检查物理内存是否耗尽。

​ 3.线程数:

ps -eLf | grep java | wc -l

​ 检查线程是否过多。

​ 4.JDK 版本

java -version

​ 不同版本对内存管理有差异(如 JDK 8 的 Metaspace vs JDK 7 的 PermGen)。

3.启用诊断工具

​ 1.GC 日志

​ 1.参数:

-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:gc.log

​ 2.分析:

​ 检查 GC 频率和回收效果。

10.123: [Full GC 9216K->9216K(9216K), 0.05 secs]

​ 老年代满,未回收。

​ 2…堆转储(Heap Dump)

​ 1.自动生成

-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dump.hprof

​ 2.手动生成

jmap -dump:live,format=b,file=dump.hprof <pid>

​ :通过 jps 获取。

​ 3.线程转储(Thread Dump)

jstack <pid> > thread.dump

用途:排查栈溢出或线程相关问题。

​ 4.监控工具

​ 1.JVisualVMJConsole:实时监控堆、线程、GC。

​ 2.jstat

jstat -gc <pid> 1000

​ 每秒输出堆使用情况。

4.根据类型排查

4.1 堆溢出(Java heap space) :

​ 1.症状:对象分配失败。

​ 2.步骤

​ 1.检查堆配置

​ -Xmx 是否太小?尝试增加:

java -Xms1g -Xmx2g -jar app.jar

​ 2.分析 GC 日志

​ 频繁 Full GC,老年代满?

​ 3.分析堆转储

​ 1.使用 Eclipse MAT 或 JVisualVM。

​ 2.查看 “Dominator Tree” 或 “Histogram”:

Class NameRetained HeapPercentage
byte[]500MB80%

​ 3.检查大对象(如 byte[]、ArrayList)的引用链。

​ 4.定位代码

​ 1.示例:无限增长的集合:

List<byte[]> list = new ArrayList<>();
while (true) list.add(new byte[1024 * 1024]);

​ 5.优化

​ 清理无用对象、限制集合大小、分片处理大数据。

4.2.方法区溢出(Metaspace 或 PermGen space) :

​ 1.症状:加载类或常量过多。

​ 2.步骤

​ 1.检查配置

​ XX:MaxMetaspaceSize 或 -XX:MaxPermSize 是否太小?

​ 增加:

-XX:MaxMetaspaceSize=512m

​ 2.分析类加载

​ 1.使用 -verbose:class 查看加载的类:

java -verbose:class -jar app.jar > class.log

​ 2.检查类数量是否异常增长。

​ 3.堆转储分析

​ 检查 java.lang.Class 实例数量。

​ 4.定位问题

​ 动态代理(如 CGLIB)、反射、类加载器泄漏。

​ 5.优化

​ 减少动态类生成、清理类加载器引用。

4.3. 栈溢出(StackOverflowError) :

​ 1.症状:递归过深。

​ 2.步骤

​ 1.检查配置

​ -Xss 是否太小?默认 1MB。

​ 增加:

-Xss2m

​ 2.分析线程转储

​ 查看调用栈:

at com.example.recursiveMethod(Example.java:10)
at com.example.recursiveMethod(Example.java:10)

​ 3.定位代码

​ 示例:

void recursiveMethod() { recursiveMethod(); }

​ 4.优化

​ 使用迭代替代递归、添加终止条件。

4.4 .无法创建线程(unable to create new native thread) :

​ 1.症状:线程过多耗尽系统资源。

​ 2.步骤

​ 1.检查线程数

ps -eLf | grep java | wc -l

​ 2.检查系统限制

ulimit -u  # 用户最大进程数
cat /proc/sys/kernel/threads-max  # 系统最大线程数

​ 3.分析线程转储

​ 检查线程状态(RUNNABLE、WAITING)。

​ 4.优化

​ 减少线程:

ExecutorService executor = Executors.newFixedThreadPool(10);

​ 调整 -Xss 减小每个线程栈大小。

4.5 .GC 开销过大(GC overhead limit exceeded):

​ 1.症状:GC 耗时长,回收少。

​ 2.步骤

​ 1.检查 GC 日志

​ 频繁 Full GC,回收率低。

​ 2.分析堆转储

​ 检查存活对象是否过多。

​ 3.优化

​ 1.增加堆大小。

​ 2.调整 GC 算法:

-XX:+UseG1GC

3.示例:堆溢出排查

1.日志:

java.lang.OutOfMemoryError: Java heap space
    at com.example.loadData(LargeData.java:30)

2.GC 日志:

[Full GC 1024M->1024M(1024M), 1.2 secs]

3.堆转储:

​ byte[] 占用 90%.

4. 代码

byte[] data = Files.readAllBytes(Paths.get("largefile.bin"));

5.优化:

try (InputStream in = Files.newInputStream(Paths.get("largefile.bin"))) {
    byte[] buffer = new byte[8192];
    while (in.read(buffer) != -1) {
        process(buffer);
    }
}

总结流程

  1. 识别类型:根据异常确定问题区域。
  2. 收集数据:JVM 参数、GC 日志、堆/线程转储。
  3. 分析原因:定位内存占用点和代码。
  4. 优化方案:调整配置或修复代码。
  5. 验证效果:监控和测试。
JVM内存溢出时,我们需要进行以下步骤进行排查: 1. 确认是否是内存溢出导致程序崩溃,可以通过查看日志或者异常信息进行确认。 2. 确认内存溢出的具体原因,可以通过jmap、jstat等命令或者工具进行查看。其中,jmap命令可以导出当前JVM内存的状态信息,包括内存使用情况、对象数量、对象大小等;jstat命令可以实时监控JVM内存的使用情况。 3. 分析内存溢出的具体原因,可以通过使用jhat、MAT等工具进行内存分析,查找内存泄漏或者过多的对象等问题。 下面是一些常用的命令: 1. jmap命令 jmap命令可以使用以下命令查看当前JVM内存的使用情况: ``` jmap -heap [pid] ``` 其中,pid表示Java进程的进程号。该命令会输出JVM内存的使用情况,包括内存大小、使用大小、最大值等。 2. jstat命令 jstat命令可以实时监控JVM内存的使用情况,可以使用以下命令: ``` jstat -gcutil [pid] [interval] [count] ``` 其中,interval表示监控间隔时间,count表示监控次数。该命令会输出JVM内存的使用情况,包括Eden区、Survivor区、Old区的使用情况等。 3. jhat命令 jhat命令可以使用以下命令生成转储文件: ``` jmap -dump:file=[filename] [pid] ``` 其中,filename表示生成的转储文件名。生成文件后,可以使用以下命令启动jhat进行分析: ``` jhat [filename] ``` 启动后,在浏览器中输入localhost:7000可以查看分析结果。 4. MAT工具 MAT(Memory Analyzer Tool)是一款开源的Java内存分析工具,可以使用以下命令进行安装: ``` sudo apt-get install eclipse-mat ``` 安装完成后,可以通过打开heap dump文件进行内存分析。 总之,对于JVM内存溢出问题,我们需要结合以上命令和工具进行全面的排查和分析,找到并解决具体的问题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值