JVM 实战 OutOfMemoryError 异常(上)

简介: 在《Java虚拟机规范》的规定里,除了程序计数器外,虚拟机内存的其他几个运行时区域都有发生OutOfMemoryError(下文称OOM)异常的可能。(本文主要是基于 jdk1.8 展开探讨)

Java 堆溢出


Java堆用于储存对象实例,我们只要不断地创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,那么随着对象数量的增加,总容量触及最大堆的容量限制后就会产生内存溢出异常。


模拟代码


下面是简单的模拟堆内存溢出的代码:


/**
 * VM Args:-Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError
 * @author xx
 * @date 2021-8-13
 */
public class HeapOOM {
    public static void main(String[] args) {
        List<byte[]> list = new ArrayList<>();
        while (true) {
            list.add(new byte[2048]);
        }
    }
}


返回结果信息如下所示:


Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
  at cn.xx.jvm.oom.HeapOOM.main(HeapOOM.java:16)


问题分析


我们需要定位是内存泄漏(Memory Leak)还是,内存溢出(Memory Overflow)


  1. 内存泄漏


  1. 内存溢出


内存泄漏


我们可以通过 jdk 自带的 jvisualvm 工具来加载堆快照文件进行分析。


如果是内存泄漏,可进一步通过工具查看泄漏对象到GC Roots的引用链,找到泄漏对象是通过怎样的引用路径、与哪些GC Roots相关联,才导致垃圾收集器无法回收它们,根据泄漏对象的类型信息 以及它到GC Roots引用链的信息,一般可以比较准确地定位到这些对象创建的位置,进而找出产生内存泄漏的代码的具体位置。


image.png


内存溢出


如果不是内存泄漏,换句话说就是内存中的对象确实都是必须存活的,那就应当检查Java虚拟机的堆参数(-Xmx与-Xms)设置,与机器的内存对比,看看是否还有向上调整的空间。再从代码上检查是否存在某些对象生命周期过长、持有状态时间过长、存储结构设计不合理等情况,尽量减少程序运 行期的内存消耗。


虚拟机栈和本地方法栈溢出


HotSpot虚拟机中并不区分虚拟机栈和本地方法栈,因此对于HotSpot来说,-Xoss参数(设置 本地方法栈大小)虽然存在,但实际上是没有任何效果的,栈容量只能由-Xss参数来设定。关于虚拟机栈和本地方法栈,在《Java虚拟机规范》中描述了两种异常:


  1. 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常。


  1. 如果虚拟机的栈内存允许动态扩展,当扩展栈容量无法申请到足够的内存时,将抛出 OutOfMemoryError异常。


《Java虚拟机规范》明确允许Java虚拟机实现自行选择是否支持栈的动态扩展,而HotSpot虚拟机 的选择是不支持扩展,所以除非在创建线程申请内存时就因无法获得足够内存而出现 OutOfMemoryError异常,否则在线程运行时是不会因为扩展而导致内存溢出的,只会因为栈容量无法 容纳新的栈帧而导致StackOverflowError异常。


虚拟机栈内存溢出


StackOverflowError


示例代码:


/**
 * VM Args:-Xss128k
 *
 * @author xx
 * @date 2021-08-13
 */
public class JavaVMStackSOF {
    private int stackLength = 1;
    public void stackLeak() {
        stackLength++;
        stackLeak();
    }
    public static void main(String[] args) throws Throwable {
        JavaVMStackSOF oom = new JavaVMStackSOF();
        try {
            oom.stackLeak();
        } catch (Throwable e) {
            System.out.println("stack length:" + oom.stackLength);
            throw e;
        }
    }
}


返回异常信息


Exception in thread "main" java.lang.StackOverflowError
stack length:992
  at cn.xx.jvm.oom.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:13)
  at cn.xx.jvm.oom.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:14)
  //.... 省略更多


OutOfMemoryError


package cn.xx.jvm.oom;
/**
 * @author xx
 * @date 2021-08-13
 */
public class JavaVMStackSOF2 {
    private static int stackLength = 0;
    public static void test() {
        long unused1, unused2, unused3, unused4, unused5, unused6, unused7, unused8, unused9, unused10, unused11,
            unused12, unused13, unused14, unused15, unused16, unused17, unused18, unused19, unused20, unused21,
            unused22, unused23, unused24, unused25, unused26, unused27, unused28, unused29, unused30, unused31,
            unused32, unused33, unused34, unused35, unused36, unused37, unused38, unused39, unused40, unused41,
            unused42, unused43, unused44, unused45, unused46, unused47, unused48, unused49, unused50, unused51,
            unused52, unused53, unused54, unused55, unused56, unused57, unused58, unused59, unused60, unused61,
            unused62, unused63, unused64, unused65, unused66, unused67, unused68, unused69, unused70, unused71,
            unused72, unused73, unused74, unused75, unused76, unused77, unused78, unused79, unused80, unused81,
            unused82, unused83, unused84, unused85, unused86, unused87, unused88, unused89, unused90, unused91,
            unused92, unused93, unused94, unused95, unused96, unused97, unused98, unused99, unused100;
        stackLength++;
        test();
        unused1 = unused2 = unused3 = unused4 = unused5 = unused6 = unused7 = unused8 = unused9 = unused10 = unused11 =
            unused12 = unused13 = unused14 = unused15 = unused16 = unused17 = unused18 = unused19 = unused20 =
                unused21 = unused22 = unused23 = unused24 = unused25 = unused26 = unused27 = unused28 = unused29 =
                    unused30 = unused31 = unused32 = unused33 = unused34 = unused35 = unused36 = unused37 = unused38 =
                        unused39 = unused40 = unused41 = unused42 = unused43 =
                            unused44 = unused45 = unused46 = unused47 = unused48 = unused49 = unused50 = unused51 =
                                unused52 = unused53 = unused54 = unused55 = unused56 = unused57 = unused58 = unused59 =
                                    unused60 = unused61 = unused62 = unused63 = unused64 = unused65 = unused66 =
                                        unused67 = unused68 = unused69 = unused70 = unused71 = unused72 = unused73 =
                                            unused74 = unused75 = unused76 = unused77 = unused78 = unused79 = unused80 =
                                                unused81 = unused82 = unused83 = unused84 = unused85 = unused86 =
                                                    unused87 = unused88 = unused89 = unused90 = unused91 = unused92 =
                                                        unused93 = unused94 = unused95 =
                                                            unused96 = unused97 = unused98 = unused99 = unused100 = 0;
    }
    public static void main(String[] args) {
        try {
            test();
        } catch (Error e) {
            System.out.println("stack length:" + stackLength);
            throw e;
        }
    }
}


输出结果:


stack length:6986
Exception in thread "main" java.lang.StackOverflowError
  at cn.xx.jvm.oom.JavaVMStackSOF2.test(JavaVMStackSOF2.java:22)
  at cn.xx.jvm.oom.JavaVMStackSOF2.test(JavaVMStackSOF2.java:22)



相关文章
|
4月前
|
监控 Java 调度
探秘Java虚拟机(JVM)性能调优:技术要点与实战策略
【6月更文挑战第30天】**探索JVM性能调优:**关注堆内存配置(Xms, Xmx, XX:NewRatio, XX:SurvivorRatio),选择适合的垃圾收集器(如Parallel, CMS, G1),利用jstat, jmap等工具诊断,解决Full GC问题,实战中结合MAT分析内存泄露。调优是平衡内存占用、延迟和吞吐量的艺术,借助VisualVM等工具提升系统在高负载下的稳定性与效率。
91 1
|
5月前
|
存储 缓存 算法
深入浅出JVM(二)之运行时数据区和内存溢出异常
深入浅出JVM(二)之运行时数据区和内存溢出异常
|
3月前
|
运维 监控 Java
(十)JVM成神路之线上故障排查、性能监控工具分析及各线上问题排错实战
经过前述九章的JVM知识学习后,咱们对于JVM的整体知识体系已经有了全面的认知。但前面的章节中,更多的是停留在理论上进行阐述,而本章节中则更多的会分析JVM的实战操作。
|
3月前
|
Arthas 存储 监控
JVM内存问题之JNI内存泄漏没有关联的异常类型吗
JVM内存问题之JNI内存泄漏没有关联的异常类型吗
|
3月前
|
缓存 监控 Java
Java虚拟机(JVM)性能调优实战指南
在追求软件开发卓越的征途中,Java虚拟机(JVM)性能调优是一个不可或缺的环节。本文将通过具体的数据和案例,深入探讨JVM性能调优的理论基础与实践技巧,旨在为广大Java开发者提供一套系统化的性能优化方案。文章首先剖析了JVM内存管理机制的工作原理,然后通过对比分析不同垃圾收集器的适用场景及性能表现,为读者揭示了选择合适垃圾回收策略的数据支持。接下来,结合线程管理和JIT编译优化等高级话题,文章详细阐述了如何利用现代JVM提供的丰富工具进行问题诊断和性能监控。最后,通过实际案例分析,展示了性能调优过程中可能遇到的挑战及应对策略,确保读者能够将理论运用于实践,有效提升Java应用的性能。 【
168 10
|
2月前
|
存储 监控 算法
深入解析JVM内部结构及GC机制的实战应用
深入解析JVM内部结构及GC机制的实战应用
|
3月前
|
缓存 算法 Java
JVM内存溢出(OutOfMemory)异常排查与解决方法
JVM内存溢出(OutOfMemory)异常排查与解决方法
|
4月前
|
存储 缓存 监控
深入JVM:解析OOM的三大场景,原因及实战解决方案
深入JVM:解析OOM的三大场景,原因及实战解决方案
|
4月前
|
运维 Java Shell
手工触发Full GC:JVM调优实战指南
本文是关于Java应用性能调优的指南,重点介绍了如何使用`jmap`工具手动触发Full GC。Full GC是对堆内存全面清理的过程,通常在资源紧张时进行以缓解内存压力。文章详细阐述了Full GC的概念,并提供了两种使用`jmap`触发Full GC的方法:通过`-histo:live`选项获取存活对象统计信息,或使用`-dump`选项生成堆转储文件以分析内存状态。同时,文中也提醒注意手动Full GC可能带来的性能开销,建议在生产环境中谨慎操作。
1046 1
|
5月前
|
Arthas Prometheus 监控
JVM工作原理与实战(四十四):JVM常见题目
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了JVM常见题目等内容。
42 1