虚拟机中常见的故障及原因
1、StackOverFlowError (栈溢出)
当申请的栈的深度超过了虚拟机所允许的最大的栈的深度,就会报 StackOverFlowError。
最常见的情况就是没有出口的递归:
public static void main(String[] args) {
stackOverFlow();
}
public static void stackOverFlow() {
stackOverFlow();
}
运行结果:
2、OutOfMemoryError: Java heap space (Java 堆空间内存泄漏)
Java堆空间不足以为新创建的对象分配内存时,就会报出 OutOfMemoryError: Java heap space
这也是很常见的一种故障情况,当创建一个超过JVM堆内存的大对象时或者一直创建对象直至堆空间饱满时,就会抛出此故障。
故障代码:
//设置虚拟机的最大栈空间和最小栈空间均为10m
// -Xms10m -Xmx10m
public static void main(String[] args) {
//创建一个80M的大对象
byte[] bytes = new byte[80 * 1024 * 1024];
}
运行结果:
3、OutOfMemoryError: GC overhead limit exceeded (超出GC开销限制)
GC回收时间过长会抛出 OutOfMemoryError: GC overhead limit exceeded。这里过长的定义是:超过98%的时间用来做GC,但却回收了不到%2的堆内存空间,连续多次GC都只回收了不到%2的极端情况才会抛出。如果不抛出 overhead limit exceeded ,那么GC回收出的内存马上又被填满,不得不再次触发GC,造成恶性循环。
错误代码:
//JVM启动配置参数如下:
//堆空间大小为10m 打印GC详情 直接内存大小为5m
//-Xmx10m -Xms10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m
public static void main(String[] args) {
int i = 0;
List<String> list = new ArrayList<>();
while (true) {
list.add(String.valueOf(++i).intern());
}
}
运行结果:
我们来看看GC打印的部分详情:
首先可以直观的看出,此错误代码会频繁的触发GC。
上面这条日志的意思是新生代回收之前为2048K,回收之后为2047K;老年代回收之前为7102K,回收之后为7102K;总栈区回收之前为9150K,回收之后为9149K。
可以看出,此时GC的效率及其低下。
4、OutOfMemoryError: Direct buffer memory (直接内存泄露)
写NIO程序时经常使用ByteBuffer来读取或写入数据,这是一种基于通道(Channel)与缓冲区(Buffer)的 I/O 方式。
它可以使用Native函数库直接分配堆外内存,也就是在本地内存分配。
ByteBuffer.allocate(capability) 第一种方式是分配JVM堆内存,属于GC的管辖范围,由于需要拷贝所以速度相对较慢。
ByteBuffer.allcateDirect(capability) 第二种方式是分配OS本地内存,不属于GC的管辖范围,由于不需要内存拷贝所以速度相对较快。
如果不断分配本地内存,堆内存很少使用,那么JVM就不需要执行GC,DirectByteBuffer对象们就不会被回收,这时候内存充足,但是本地内存可能已经使用光了,再次尝试分配本地内存就会出现 OutOfMemoryError: Direct buffer memory ,那么程序就直接崩溃了。
查看本地内存空间大小:
public static void main(String[] args) {
System.out.println("默认的直接内存大小:"+sun.misc.VM.maxDirectMemory() / (1024*1024)+"MB");
}
运行结果:
一般默认情况下,本地内存大约为电脑运行内存的四分之一(我的电脑是8G)
为了更直观的看到此故障,我们要把这个空间设置的小一点,参数和故障代码如下:
//-Xmx10m -Xms10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m(同上个故障)
public static void main(String[] args) {
System.out.println("配置的直接内存大小:"+sun.misc.VM.maxDirectMemory() / (1024*1024)+"MB");
// 直接内存大小为5m,在本地内存上分配一个6m的大对象
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(6 * 1024 * 1024);
}
运行结果:
5、OutOfMemoryError: Unable to create new native thread (无法创建新的本地线程)
- 你的引用创建了太多的线程,超过了系统承载的极限。
- 你的服务器不允许你的应用创建这么多的线程,Linux系统默认允许单个进程可以创建的线程数是1024个,你的应用超过了这个数量,就会报OutOfMemoryError: Unable to create new native thread。
故障代码如下:
public static void main(String[] args) {
//死循环创建线程
for (int i = 0; ; i++) {
new Thread(() -> {
System.out.println("cccc");
}).start();
}
}
将此代码拷贝到 Linux 上运行,运行结果如下: