Java内存溢出OOM解决方案

2025-01-04  本文已影响0人  sunpy

什么是OOM


OOM是“Out Of Memory”的缩写,即内存溢出,是指应用系统中存在无法回收的内存或使用的内存过多,最终使得程序运行要用到的内存大于能提供的最大内存,导致程序无法继续正常运行。

dump文件和hprof区别


dump文件

hprof文件

JVM导出dump文件方法


1、jmap命令导出dump文件

## java_pid java进程id,可以用jps命令查看
jmap -dump:live,format=b,file=/home/heap.hprof java_pid

2、java虚拟机参数自动生成

## 当OutOfMemoryError发生时自动生成 Heap Dump 文件。
-XX:+HeapDumpOnOutOfMemoryError
## 指定 dump 文件存储路径
-XX:HeapDumpPath=/home/dumps

什么情况下会出现OOM


堆内存溢出、栈内存溢出、元空间内存溢出、直接内存溢出、老年代内存溢出

堆内存溢出

存放类别 解释
普通对象 通过new关键字创建的对象实例,如new MyClass()创建的MyClass对象,都会被分配到堆内存中。对象的实例字段(属性)和对象本身都存储在堆内存里.
数组对象 包括基本数据类型的数组(如int[]、String[]等)和对象数组(如MyClass[]),它们的元素也存储在堆内存中
对象头 每个对象在堆内存中都有一个对象头,用于存储对象的元数据信息,如对象的哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等信息,以及指向对象类元数据的指针(Klass指针)
对象引用 对象之间的引用关系(如一个对象的字段是另一个对象的引用)也存储在堆内存中。对象引用本身是一个指向堆内存中对象地址的指针
动态字段 在运行时动态添加到对象的字段(如通过反射机制添加的字段)也会存储在堆内存中.
动态方法 在运行时动态添加到对象的方法(如通过反射机制添加的方法)的相关信息也会存储在堆内存中,但方法的实现代码本身通常存储在方法区(元空间)中.
临时对象 在方法调用过程中创建的临时对象,如局部变量对象、中间计算结果对象等,也会被分配到堆内存中.
克隆对象 通过对象的clone()方法创建的对象克隆也会存储在堆内存中.
序列化对象 通过序列化机制将对象转换为字节流时,序列化对象的数据也会存储在堆内存中,直到被写入到文件或网络传输出去.
反序列化对象 通过反序列化机制将字节流还原为对象时,反序列化生成的对象也会存储在堆内存中.
缓存对象 一些框架或应用程序可能会使用缓存机制将对象存储在堆内存中,以便快速访问和重用这些对象.
池化对象 对于一些频繁创建和销毁的对象,如连接对象、线程对象等,可能会使用对象池技术将这些对象存储在堆内存中,以提高性能和资源利用率.
@RequestMapping("/oom")
@RestController
public class OutOfMemoryController {

    @GetMapping("/doTest")
    public String doTest() {
        List<Object[]> container = new ArrayList<>();

        while(true) {
            container.add(new Object[1024 * 1024 * 10]);
        }
    }
}

栈内存溢出

存放类别 解释
局部变量表 用于存储方法的局部变量和方法参数。局部变量表的大小在方法被编译时确定,并存储在方法的字节码中。局部变量可以是基本数据类型、对象引用或返回地址等.
操作数栈 用于存储操作数和中间计算结果。操作数栈的大小也由方法的字节码决定。在方法执行过程中,操作数栈会根据字节码指令进行入栈和出栈操作,以完成各种计算和操作.
动态链接信息 包含当前方法的调用者信息,如调用者的类、方法名、方法描述符等。这些信息用于实现方法调用的动态链接,确保正确调用父类或接口中的方法.
方法返回地址 存储方法执行完毕后返回到的位置。当方法执行完毕后,会根据返回地址跳转到调用者的方法中继续执行.
@RequestMapping("/oom")
@RestController
public class OutOfMemoryController {

    @GetMapping("/doTest")
    public String doTest() {
        repeatExecute();

        return "HelloWorld";
    }

    public void repeatExecute() {
        repeatExecute();
    }
}

元空间内存溢出

元空间内存主要存放:类的元数据、运行时常量池、类加载器的数据、垃圾回收信息。

存放类别 解释 归类
类的结构信息 包括类的名称、修饰符、超类和接口信息、字段和方法信息等 类的元数据
常量池信息 存储类的常量池中的常量,如字符串常量、类常量、字段常量等。这些常量在类加载过程中被解析和存储. 类的元数据
字段和方法的元数据 包括字段的名称、类型、修饰符等信息,以及方法的名称、参数类型、返回类型、修饰符、字节码等信息 类的元数据
类的继承关系信息 存储类的继承关系,如超类和子类的信息,以及类与接口之间的实现关系 类的元数据
类的静态变量 存储类的静态变量的值。静态变量是类级别的变量,属于类的元数据的一部分 类的元数据
字符串常量池 存储字符串常量,包括字符串字面量和通过intern()方法添加的字符串常量. 字符串常量池在Java 7及以后版本中从永久代移动到了堆内存中,但在元空间中仍然存储一些与字符串相关的元数据信息 运行时常量池
类常量池 存储类的常量池中的其他常量,如类常量、字段常量等. 运行时常量池
本地机器码 JVM的即时编译器(JIT)会将字节码编译成本地机器码,这些编译后的代码也会存储在元空间中,以便快速执行. JIT编译器生成的代码
类加载器的元数据 存储类加载器的元数据信息,如类加载器的名称、父类加载器信息等. 类加载器的数据
已加载类的记录 记录已经加载的类的信息,用于类加载的检查和管理. 类加载器的数据
类的卸载信息 存储类的卸载信息,用于垃圾回收器在类卸载时进行清理和回收. 垃圾回收信息
@RequestMapping("/oom")
@RestController
public class OutOfMemoryController {
    
    @GetMapping("/doTest")
    public String doTest() {
        while (true) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(SelfDefineClass.class);
            enhancer.setCallback(new ProxyDefineClass());
            enhancer.setUseCache(false);
            enhancer.create();
        }
    }
}

jvisualvm工具分析OOM问题


-Xmx64m
-Xms64m
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=F:\\temp\\err.hprof

注意


前面基于jvm内存分布情况分析为JDK8

上一篇 下一篇

猜你喜欢

热点阅读