这些 OutOfMemoryError
类型错误表示不同的内存问题:
java.lang.OutOfMemoryError: Java heap space
:
-Xmx
(最大堆大小)和 -Xms
(初始堆大小)。java.lang.OutOfMemoryError: Metaspace
:
-XX:MaxMetaspaceSize
。java.lang.OutOfMemoryError: Direct buffer memory
:
ByteBuffer.allocateDirect
)和某些高性能I/O操作。直接内存不在Java堆中,而是在操作系统的本地内存中。-XX:MaxDirectMemorySize
。jcmd
命令或其他监控工具来跟踪直接内存的使用情况。针对每种类型的 OutOfMemoryError
,正确的解决方案通常包括对应用程序的内存管理进行优化、调整JVM参数,以及使用合适的工具来分析和监控内存使用情况。
要让Java程序出现 java.lang.OutOfMemoryError: Java heap space 错误,通常是通过模拟一种过度使用堆内存的情况
要让Java程序出现 java.lang.OutOfMemoryError: Java heap space
错误,通常是通过模拟一种过度使用堆内存的情况。以下是一些常见的方法来实现这一点:
通过创建大量对象,尤其是内存消耗大的对象,可以迅速耗尽堆内存。以下是一个简单的示例程序:
import java.util.ArrayList;
import java.util.List;
public class HeapSpaceOOM {
public static void main(String[] args) {
// List to hold references to objects
List<Object> objects = new ArrayList<>();
// Continuously add new objects to the list
while (true) {
// Add a large object to the list
byte[] largeArray = new byte[1024 * 1024]; // 1 MB
objects.add(largeArray);
}
}
}
在这个示例中,程序不断向列表中添加1MB的字节数组。随着对象数量的增加,堆内存会逐渐耗尽,最终触发 java.lang.OutOfMemoryError: Java heap space
错误。
通过调整JVM的堆内存参数来限制堆内存的使用:
-Xmx
参数来设置最大堆内存,例如 -Xmx10m
设置最大堆内存为10MB。-Xms
参数来设置初始堆内存,例如 -Xms10m
设置初始堆内存为10MB。例如,运行程序时设置:
java -Xmx10m HeapSpaceOOM
这将限制堆内存的最大值为10MB。如果程序中的内存使用超出了这个限制,会触发 OutOfMemoryError
。
创建一个模拟内存泄漏的示例,例如:
import java.util.HashMap;
import java.util.Map;
public class HeapSpaceLeak {
// Static map to hold references
private static Map<String, String> leakMap = new HashMap<>();
public static void main(String[] args) {
// Continuously add entries to the map
int count = 0;
while (true) {
leakMap.put("key" + count, new String(new char[1024 * 1024])); // 1 MB strings
count++;
}
}
}
在这个示例中,程序将大量的1MB字符串添加到静态的 HashMap
中,从而造成堆内存不断增长,直到耗尽。
Thread.sleep
模拟慢增长如果需要慢慢增加内存使用,可以加入 Thread.sleep
:
import java.util.ArrayList;
import java.util.List;
public class SlowHeapSpaceOOM {
public static void main(String[] args) throws InterruptedException {
List<Object> objects = new ArrayList<>();
while (true) {
byte[] largeArray = new byte[1024 * 1024]; // 1 MB
objects.add(largeArray);
Thread.sleep(100); // 每100ms增加一个对象
}
}
}
通过这些方法,可以模拟不同情况导致的 java.lang.OutOfMemoryError: Java heap space
错误。请确保在非生产环境中进行这些测试,以免影响系统的稳定性。
public static void main(String[] args) {
// SpringApplication.run(CommonPureServiceApplication.class, args);
List<byte[]> list = new ArrayList<>();
Runnable allocateMemory = () -> {
while (true) {
list.add(new byte[10 * 1024 * 1024]);
// 每隔1秒打印一次已分配的对象数,便于观察
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
// 启动多个线程来加速内存分配
for (int i = 0; i < 10; i++) {
new Thread(allocateMemory).start();
}
}
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>fun.acowbo.CommonPureServiceApplication
</mainClass>
</configuration>
</plugin>
</plugins>
</build>
java -Xmx2g -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./heapdump.hprof -jar CommonPureService-0.0.1-SNAPSHOT.jar
说明
-Xmx2g
参数
-Xmx2g
是 JVM 参数之一,用于设置 Java 应用程序的最大堆内存大小。这个参数会直接影响到应用程序在发生 OutOfMemoryError
(OOM)时的行为。以下是一些关键点,解释了 -Xmx2g
参数如何影响 OOM:
-Xmx2g
指定了 JVM 堆的最大大小为 2GB。这意味着 Java 应用程序可以使用的最大内存量为 2GB。OutOfMemoryError
。-Xmx
设置为 2GB: java.lang.OutOfMemoryError
。如果应用程序尝试分配更多内存而堆已经满了,就会触发 OOM。-XX:+HeapDumpOnOutOfMemoryError
:
OutOfMemoryError
时自动生成堆转储文件(.hprof
),用于分析内存使用情况。-XX:HeapDumpPath=./heapdump.hprof
:
-Xmx
设置得较高(例如 4GB),应用程序将有更多的内存可用,这可能会延迟 OOM 的发生。然而,如果应用程序存在内存泄漏或异常高的内存需求,最终还是会触发 OOM。-Xmx
值可以帮助确保应用程序在内存不足时不会过早地触发 OOM。不过,合理的内存配置应根据应用程序的内存需求和实际运行情况来决定。假设你使用以下命令运行 Java 应用程序:
java -Xmx2g -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./heapdump.hprof -jar CommonPureService-0.0.1-SNAPSHOT.jar
heapdump.hprof
,该文件包含应用程序内存状态的快照。你可以使用工具(如 JProfiler、Eclipse MAT)来分析这个堆转储文件,以了解内存使用情况和找出问题所在。使用jconsole
也可以看到以下结果
在上面的异常出现的同时JVM 会生成一个堆转储文件
heapdump.hprof
使用JProfile导入刚刚生成的文件,可以明显看出byte[]占了将近两个G
选择上图中的最大对象,会出现如下所示,可以看到ArrayList占了99%
右键java.util.ArrayList选择使用选定对象,再选择引用
选择传入引用,可以定位到问题
在你提供的 Java 代码中,涉及到对象的传递和引用,这里可以通过以下方式说明"传入引用"和"传出引用"的概念:
import java.util.ArrayList;
import java.util.List;
public class MemoryAllocationExample {
public static void main(String[] args) {
// 创建一个 List 用于存储分配的 byte 数组
List<byte[]> list = new ArrayList<>();
// Runnable 任务,用于不断分配内存
Runnable allocateMemory = () -> {
while (true) {
list.add(new byte[10 * 1024 * 1024]); // 每次分配 10MB 的 byte 数组
// 每隔1秒打印一次已分配的对象数,便于观察
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
// 启动多个线程来加速内存分配
for (int i = 0; i < 10; i++) {
new Thread(allocateMemory).start();
}
}
}
Runnable
的实现 (allocateMemory
) 是通过匿名内部类的方式定义的。这个 Runnable
对象实际上引用了 list
变量。allocateMemory
时,每个线程都持有对同一个 list
对象的引用。因为这个 list
是在 main
方法中创建的并且传递给了 Runnable
,每个线程都在修改这个共享的 list
对象。Runnable allocateMemory = () -> {
while (true) {
list.add(new byte[10 * 1024 * 1024]); // list 是通过引用传递给线程的
// ...
}
};
list
的修改会反映在所有线程中,因为它们共享同一个 list
实例的引用。List<byte[]>
对象,如下所示:public static List<byte[]> createList() {
List<byte[]> newList = new ArrayList<>();
return newList; // 返回 List 对象的引用
}
createList()
方法时,它返回一个 List<byte[]>
对象的引用。调用者可以通过这个引用访问和修改 newList
对象。newList
对象,因为他们得到的是对原始对象的引用,而不是对象的副本。在你的代码中,传入引用 和 传出引用 的概念通过 list
的使用体现了出来:
Runnable
实例持有对 list
的引用,所有线程操作的是同一个 list
对象。list
的示例,但你可以理解为 list
对象是在 main
方法中创建的,并且它的引用被传递给了 Runnable
,从而允许多个线程共享和修改同一个 list
。这种引用传递的机制允许对象在不同的代码块和线程之间共享和修改数据,从而影响程序的行为。