
在生产环境抢修中,我们经常会碰到应用系统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内存溢出问题深入刨析
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/225723.html