在介绍之前先简单介绍下 直接内存(Direct Memory)和堆内存(Heap Memory):
-
关系:
- 直接内存并不是Java虚拟机的一部分,它是通过Java的NIO库中的ByteBuffer来分配和管理的。直接内存通常由操作系统的本地内存(Native Memory)提供支持。
- 堆内存是Java虚拟机的一部分,用于存储Java对象。堆内存由Java虚拟机的堆内存分配器来分配和管理。
-
区别:
- 分配方式:直接内存是通过调用操作系统的本地内存分配函数来分配的,而堆内存是由Java虚拟机的堆内存分配器来分配的。
- 受限性:直接内存不受Java堆大小的限制,它可以分配的内存量通常受限于操作系统本地内存的大小。而堆内存的大小受Java虚拟机的堆内存分配器和-Xmx参数的限制。
- 垃圾回收:直接内存不受Java对象垃圾回收的影响,因此,直接内存的分配和释放不会触发Java堆的垃圾回收操作。而堆内存中的Java对象会受到Java堆的垃圾回收操作的影响。
- 性能:直接内存的分配和释放通常比堆内存更高效,因为它直接利用了操作系统的本地内存管理机制。而堆内存的分配和释放涉及到Java虚拟机的堆内存分配器的复杂逻辑。
总结起来,直接内存和堆内存是Java中两种不同的内存区域,它们有着不同的分配方式、受限性、垃圾回收机制和性能特点。
Java 中的内存溢出是很常见的场景,除了程序计数器外,JVM内存的其他几个运行时区域都有可能发生 OutOfMemoryError(OOM)异常。下面通过一些场景来验证JVM不同内存区域造成 OOM 的情况。
内存溢出分类
内存溢出,我们可以想象一下生活中的场景,比如我们给水杯倒水,当水杯满了后,继续倒水,就会出现水溢出了。我们把水杯比作内存区域,水比作对象,当对象创建多了,又没有及时把水倒掉或者喝掉,就会越倒越多,最后水漫出来了。
JVM 中最常见的两个错误就是 StackoverflowError(栈溢出) 和 OutofMemoryError(堆溢出)。而 OutOfMemoryError 又有 5 种异常。
- java.lang.OutOfMemoryError:java heap space(堆内存溢出异常)
- java.lang.OutOfMemoryError:GC overhead limit exceeded(GC 回收时间过长)
- java.lang.OutOfMemoryError:Direct buffer memory(直接内存异常)
- java.lang.OutOfMemoryError:unable to create new native thread(创建线程数达到上限异常)
- java.lang.OutOfMemoryError:Metaspace(元空间内存不足)
StackoverflowError 和 OutofMemoryError 属于 Error,如下图的类继承关系图。
栈溢出异常
如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出 StackOverflowError 异常。
栈溢出的场景,最简单的方式是多次递归调用。分配的栈空间大小一般为 512 KB,如果用完了,则会抛出异常。
编写代码
package com.test;
public class TestStackOverflowError {
public static void main(String[] args) {
stackOverflowError();
}
private static void stackOverflowError() {
stackOverflowError();
}
}
运行程序
Exception