JAVA OOM内存溢出问题深入解析

 更新时间:2023年10月07日 15:53:46   作者:两年  
这篇文章主要为大家介绍了JAVA OOM内存溢出问题深入解析,在生产环境抢修中,我们经常会碰到应用系统java内存OOM的情况,这个问题非常常见,今天我们就这个问题来深入学习探讨一下

JAVA内存OOM溢出的几种常见业务场景

当生产系统遇到莫种场景时就会触发OOM内存溢出,导致系统不可用。常见的场景如:

  • 秒杀活动:当大量的请求进来的时候,导致系统瞬间创建大量的内存对象,如果内存对象都是大对象而且不能及时释放,就会撑爆内存
  • 特殊业务场景:如获取报表数据,报表信息需要从数据库中查询大量的数据到内存中进行处理,如果对数据没有控制的话,就会撑爆内存
  • 内存空间分配过小:如tomcat类的web容器分配的java内存空间过小,当过多的线程创建的对象直接把内存用完
  • 创建的类过多,导致永久代或者元空间被消耗光

JAVA内存结构原理

  • 类的加载

  • 编写好的java文件通过IDE编译,变成.class二进制字节文件
  • 该字节文件加载进类加载器

详细过程如下,java程序运行时,虚拟机会单独划分出一块内存区域,我们需要特别关注堆和栈这两块区域,生产抢修频繁出问题的也是这两个区域。(堆负责数据存储,栈则负责程序运行

  • JAVA 堆

Java 堆区还可以划分为新生代和老年代,新生代又可以进一步划分为 Eden 区、Survivor 1 区、Survivor 2 区。

该区域的主要特点:

  • 该区域是程序的数据存储区域,主要负责存放new对象,但不存放数据类型和对象引用
  • 该区域是垃圾回收的主要工作区域
  • JAVA栈

存放基本数据类型(boolean、byte、char、short、int、float、long、double)以及对象的引用(它可能是一个指向对象起始地址的引用指针,也可能指向一个代表对象的句柄或者其他与此对象相关的位置)。

这个区域可能有两种异常:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出 StackOverflowError 异常;如果虚拟机栈可以动态扩,当扩展时无法申请到足够的内存时会抛出 OutOfMemoryError异常。

设计程序演示内存溢出现象

为了更好的理解程序是如何运行,并导致JAVA内存溢出的,我设计了四个场景来进行模拟演示

  • 栈溢出(StackOverflowError)
  • 堆溢出(OutOfMemoryError:Java heap space)
  • 永久代溢出 java8之前版本(OutOfMemoryError: PermGen space)。java8之后(包含)版本(OutOfMemoryError: PermGen space)
  • 直接内存溢出
  • 栈溢出(StackOverflowError)

线程请求的栈深度大于虚拟机所允许的深度,将抛出 StackOverflowError 异常

/**
 * 栈溢出
 */
public class Stack {
    public static void main(String[] args) {
        new Stack().test();
    }
    public void test() {
        test();
    }
}

栈深度被消耗殆尽抛出异常

Exception in thread "main" java.lang.StackOverflowError
        at Stack.test(Stack.java:11)
        at Stack.test(Stack.java:11)
        at Stack.test(Stack.java:11)

  • 堆溢出(OutOfMemoryError:Java heap space)

通过不断的往数组列表中存放大对象,最终把内存资源消耗殆尽

import java.util.ArrayList;
import java.util.List;
public class HeapOOMTest {
    public static void main(String[] args) {
        List<byte[]> list = new ArrayList<>();
        try {
            while (true) {
                // 分配大量的内存,每次分配1MB的字节数组
                byte[] data = new byte[1024 * 1024]; // 1MB
                list.add(data);
            }
        } catch (OutOfMemoryError e) {
            System.out.println("发生堆溢出错误:Java heap space");
            e.printStackTrace();
        }
    }
}

抛出异常

发生堆溢出错误:Java heap space
java.lang.OutOfMemoryError: Java heap space
        at HeapOOMTest.main(HeapOOMTest.java:11)

  • 永久代溢出 java8之前版本(OutOfMemoryError: PermGen space)。java8之后(包含)版本(OutOfMemoryError: Metaspace)

java1.8之后就舍弃了永久代,取而代之的是元空间。我的环境是jdk1.8,所以只能演示元空间的报错。

这段Java代码演示了如何在Java程序中触发"OutOfMemoryError: Metaspace"错误。它通过不断生成新的类、加载这些类并将其加入到列表中,最终导致"Metaspace"区域的内存耗尽。

具体流程如下:

  • main方法中,通过ArrayList创建一个classes列表,用于存储动态生成的类。
  • while(true)循环中,程序不断执行以下操作:

    • 生成一个唯一的类名("DynamicClass" + 当前纳秒时间戳)。
    • 使用generateClassBytes方法生成类的字节码。
    • 将生成的字节码写入到以类名命名的文件中。
    • 使用自定义的CustomClassLoader类加载器加载这个类。
    • 将加载后的类对象添加到classes列表中。
    • 可选地,删除已经加载的类文件,释放磁盘空间。
  • 当"Metaspace"区域的内存耗尽时,会抛出"OutOfMemoryError: Metaspace"异常,程序捕获并打印异常信息。
import java.util.List;
import java.util.ArrayList;
import java.nio.file.*;
import java.io.IOException;
public class PermGenOutOfMemory {
    public static void main(String[] args) {
        List<Class<?>> classes = new ArrayList<>();
        try {
            while (true) {
                // Generate a unique class name and bytecode
                String className = "DynamicClass" + System.nanoTime();
                byte[] classData = generateClassBytes(className);
                // Define the class by writing it to a file and loading it
                Path classFilePath = Paths.get(className + ".class");
                Files.write(classFilePath, classData);
                Class<?> clazz = loadClass(className, classFilePath);
                classes.add(clazz);
                // Optional: Delete the class file after loading
                Files.delete(classFilePath);
            }
        } catch (OutOfMemoryError | IOException e) {
            System.out.println("OutOfMemoryError: Metaspace");
            e.printStackTrace();
        }
    }
    static byte[] generateClassBytes(String className) {
        // Replace this with your own class bytecode generation logic
        // You can use libraries like ASM for bytecode generation
        // For simplicity, let's just create a class with a single method
        String classContent = "public class " + className + " { public void doSomething() { System.out.println(\"Hello from " + className + "\"); } }";
        return classContent.getBytes();
    }
    static Class<?> loadClass(String className, Path classFilePath) throws IOException {
        // Load the class from the file into the Metaspace
        byte[] classData = Files.readAllBytes(classFilePath);
        return new CustomClassLoader().loadClass(className, classData);
    }
    static class CustomClassLoader extends ClassLoader {
        public Class<?> loadClass(String name, byte[] classData) {
            return defineClass(name, classData, 0, classData.length);
        }
    }
}
  • 直接内存溢出

分配 1MB 的直接内存并将其累加到 allocatedMemory 中,使用 unsafe.allocateMemory(memory) 分配直接内存。

如果在分配直接内存时发生异常,则会捕获异常并打印异常堆栈信息。

import sun.misc.Unsafe;
import java.lang.reflect.Field;
public class DirectMemoryOOMTest {
    public static void main(String[] args) {
        int i = 0;
        long allocatedMemory = 0;
        Unsafe unsafe = null;
        try {
            Field field = Unsafe.class.getDeclaredFields()[0];
            field.setAccessible(true);
            unsafe = (Unsafe) field.get(null);
            while (true) {
                long memory = 1024 * 1024;
                allocatedMemory += memory;
                unsafe.allocateMemory(memory);
                i++;
            }
        } catch (Exception e) {
            e.printStackTrace();
         } 
    }
}

Exception in thread "main" java.lang.OutOfMemoryError
        at sun.misc.Unsafe.allocateMemory(Native Method)
        at DirectMemoryOOMTest.main(DirectMemoryOOMTest.java:19)
PS D:\java>

总结

  • 了解了java内存结构原理和生产主要发生问题的区域
  • 了解了哪些业务场景会导致的OOM
  • 通过代码演示了几种导致OOM问题的情况

以上就是JAVA内存溢出问题深入解析的详细内容,更多关于JAVA 内存溢出的资料请关注脚本之家其它相关文章!

相关文章

  • Java函数式编程之通过行为参数化传递代码

    Java函数式编程之通过行为参数化传递代码

    行为参数化就是可以帮助你处理频繁变更的需求的一种软件开发模式,这篇文章将给大家详细的介绍一下Java函数式编程之行为参数化传递代码,感兴趣的同学可以参考阅读下
    2023-08-08
  • java导出Excel(非模板)可导出多个sheet方式

    java导出Excel(非模板)可导出多个sheet方式

    Java开发中,导出Excel是常见需求,有时需要支持多个Sheet导出,此技巧介绍非模板方式实现单标题单Sheet以及多Sheet导出,标题一致或不一致均可,可换成Map使用,适合个人开发者和需要Excel导出功能的场景
    2024-09-09
  • Java接口DAO模式代码原理及应用详解

    Java接口DAO模式代码原理及应用详解

    这篇文章主要介绍了Java接口DAO模式代码原理及应用详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-11-11
  • Java中的通用路径转义符介绍

    Java中的通用路径转义符介绍

    这篇文章主要介绍了Java中的通用路径转义符介绍,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-12-12
  • 使用记事本编写java程序全过程图解

    使用记事本编写java程序全过程图解

    这篇文章主要介绍了如何使用记事本编写java程序,需要的朋友可以参考下
    2014-03-03
  • 基于HTTP协议实现简单RPC框架的方法详解

    基于HTTP协议实现简单RPC框架的方法详解

    RPC全名(Remote Procedure Call),翻译过来就是远程过程调用,本文将为大家介绍如何基于HTTP协议实现简单RPC框架,感兴趣的小伙伴可以了解一下
    2023-06-06
  • JPA中@ElementCollection使用示例详解

    JPA中@ElementCollection使用示例详解

    在JPA中,@ElementCollection注解主要用于映射集合属性,例如List、Set或数组等集合属性,以及Map结构的集合属性,每个属性值都有对应的key映射,这篇文章主要介绍了JPA中@ElementCollection使用,需要的朋友可以参考下
    2023-11-11
  • 轻松掌握Java工厂模式、抽象工厂模式

    轻松掌握Java工厂模式、抽象工厂模式

    这篇文章主要帮助大家轻松掌握Java工厂模式、抽象工厂模式,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-10-10
  • 使用ClassFinal实现SpringBoot项目jar包加密的操作指南

    使用ClassFinal实现SpringBoot项目jar包加密的操作指南

    在实际开发中,保护项目的安全性和保密性是至关重要的,针对于 Spring Boot 项目,我们需要将 JAR 包进行加密从而有效地防止未经授权的访问和修改,本文将介绍如何使用ClassFinal在 Spring Boot 项目中实现 JAR 包加密,需要的朋友可以参考下
    2024-06-06
  • Java实现图片上传到服务器并把上传的图片读取出来

    Java实现图片上传到服务器并把上传的图片读取出来

    在各大网站上都可以实现上传头像功能,可以选择自己喜欢的图片做头像,从本地上传,今天小编给大家分享Java实现图片上传到服务器并把上传的图片读取出来,需要的朋友参考下
    2017-02-02

最新评论