StackOverflowError 是一个java中常出现的错误:在jvm运行时的数据区域中有一个java虚拟机栈,当执行java方法时会进行压栈弹栈的操作。在栈中会保存局部变量,操作数栈,方法出口等等。jvm规定了栈的最大深度,当执行时栈的深度大于了规定的深度,就会抛出StackOverflowError错误。
代码示例:
public class StackoverFlowErrorTest {
public static void main(String[] args) {
test();
}
// 递归调用
public static void test() {
test();
}
}
报错信息:
最容易引起错的是,递归没有加入退出条件,容易引起StackOverflowError
java的堆内存不够用了才会报OutOfMemoryError:Java heap space
这个错误。
引发 java.lang.OutOfMemoryError: Java heap space 错误的主要原因就是在创建新的对象时, 堆内存中的空间不足以存放新创建的对象时发生!
public class JavaHeapSpaceErrorTest {
public static void main(String[] args) {
byte[] bytes = new byte[11*1024*1024];
}
}
GC回收事时间过长会抛出此异常。过长的定义是,超过98%的时间用来左GC并且回收了不到2%的堆内存。
OutOfMemoryError是java.lang.VirtualMachineError的子类,当JVM资源利用出现问题时抛出,更具体地说,这个错误是由于JVM花费太长时间执行GC且只能回收很少的堆内存时抛出的。根据Oracle官方文档,默认情况下,如果Java进程花费98%以上的时间执行GC,并且每次只有不到2%的堆被恢复,则JVM抛出此错误。换句话说,这意味着我们的应用程序几乎耗尽了所有可用内存,垃圾收集器花了太长时间试图清理它,并多次失败。
在这种情况下,用户会体验到应用程序响应非常缓慢,通常只需要几毫秒就能完成的某些操作,此时则需要更长的时间来完成,这是因为所有的CPU正在进行垃圾收集,因此无法执行其他任务。
错误重现:
public class GCOverHeadErrorTest {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
int i = 0;
while (true) {
// 放回常量池中的引用,在常量池中创建对象
String value = String.valueOf(++i).intern();
list.add(value);
}
}
}
解决方案
理想的解决方案是通过检查可能存在内存泄漏的代码来发现应用程序所存在的问题,这时需要考虑:
我们还可以使用自动化图形工具,比如JVisualVM、JConsole,它可以帮助检测代码中的性能问题,包括java.lang.OutOfMemoryError。
最后一种方法是通过更改JVM启动配置来增加堆大小,或者在JVM启动配置里增加-XX:-UseGCOverheadLimit选项来关闭GC Overhead limit exceeded。但是这样治标不治本,不推荐。
导致原因
导致这个的原因是:用 nio ,但是 direct buffer 不够。
写NIO程序经常使用ByteBuffer来读取或者写入 数据 ,这是一种基于通道(channel)和缓冲区(Buffer)的I/O方式。
它可以使用Native函数库直接分配堆外内存,然后通过一个存储在 java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。
这样能在一些场景中显著的提高性能,因为避免了在Java堆和Native堆中来回复制数据
- ByteBuffer.allocate(capability) :这种方式是分配JVM堆内存,属于GC管辖范围,由于需要拷贝数据较慢。
- ByteBuffer.allocateDirect(capability):这种方式是分配OS本地内存,不属于GC管辖范围,由于不需要内存拷贝,所以速度相对较快。
但是如果不断的分配本地内存,堆内存很少使用,那么JVM就不需要执行GC,DirectByteBuffer对象们 就不会被回收。这时候内存充足,但是本地内存可能被用光,再次分配的时候就会报:OutOfMemoryError:Direct buffer memory错误,程序直接崩溃
代码示例
先分配堆外内存-XX:MaxDirectMemorySize=5m
代码:
public class DirectBufferMemoryTest {
public static void main(String[] args) {
// 查看JVM分配的最大的本地内存
long memory = VM.maxDirectMemory()/1024/1024;
System.out.println("JVM分配的最大DirectMemory内存是:"+ memory + "M");
// allocateDirect分配堆外内存。不在堆内
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(6 * 1024 * 1024);
}
}
运行结果:
从结果可以看出,当堆外内存不足的时候,就会报OutOfMemoryError:Direct buffer memory这个错误。
高并发请求服务器时,有可能会出现这个异常
导致原因:
1. 应用创建了太多线程了,一个应用进程创建多个线程,超过系统承载极限
2. 服务器不允许应用程序创建这么多线程,linux系统默认允许单个进程创建的线程是1024个,如果超过这个数量,就可能报这个错误
如下代码在linux系统允许,由于不断的创建线程,就会报这个错误。
public class UnableToCreateThreadTest {
public static void main(String[] args) {
for (int i = 0; ;i++) {
System.out.println("================:" + i);
new Thread(new Runnable() {
@Override
public void run() {
}
}).start();
}
}
}
解决办法:
这个错误是JVM的元空间内存不够用了才会报这个错误,JDK8后,把永久代改为元空间,元空间使用的是本地内存,不在是堆内存
我们先查看元空间JVM默认的大小是多少
public class MetaspaceTest {
public static void main(String[] args) throws InterruptedException {
System.out.println("===========");
Thread.sleep(Integer.MAX_VALUE);
}
}
使用jps查看进程编号和jinfo命令查看信息:
C:\Users\18133\IdeaProjects\javastudy>jinfo -flag MetaspaceSize 12716
-XX:MetaspaceSize=21807104
从结果可以看出元空间的大小 是 21807104/1024/1024 = 20.8M,不到21M
java.lang.OutOfMemoryError: Metaspace 错误所表达的信息是: 元数据区(Metaspace) 已被用满