1、java.lang.StackOverflowError
报这个错误一般是由于方法深层次的调用,默认的线程栈空间大小一般与具体的硬件平台有关。栈内存为线程私有的空间,每个线程都会创建私有的栈内存。栈空间内存设置过大,创建线程数量较多时会出现栈内存溢出StackOverflowError。同时,栈内存也决定方法调用的深度,栈内存过小则会导致方法调用的深度较小,如递归调用的次数较少。
Demo:
public class StackOverFlowErrorDemo {
static int i = 0;
public static void main(String[] args) {
stackOverflowErrorTest();
}
private static void stackOverflowErrorTest() {
i++;
System.out.println("这是第 "+i+" 次调用");
stackOverflowErrorTest();
}
}
//运行结果:
。。。。
这是第 6726 次调用
这是第 6727 次调用
Exception in thread "main" java.lang.StackOverflowError
。。。。
注意:这是一个Error!!!!
2、java.lang.OutOfMemoryError: Java heap space
Heap size 设置 JVM堆的设置是指:java程序执行过程中JVM能够调配使用的内存空间的设置。JVM在启动的时候会自己主动设置Heap size的值,其初始空间(即-Xms)是物理内存的1/64,最大空间(-Xmx)是物理内存的1/4。能够利用JVM提供的-Xmn -Xms -Xmx等选项可进行设置。Heap size 的大小是Young Generation 和Tenured Generaion 之和。
Demo:
public class OOMHeapSpaceDemo {
public static void main(String[] args) {
byte[] bytes = new byte[30*1024*1024];
}
}
然后修改堆内存的初始容量和最大容量为5MB
运行程序,查看结果:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at jvm.OOMHeapSpaceDemo.main(OOMHeapSpaceDemo.java:7)
注意:这是一个Error!!!!
3、java.lang.OutOfMemoryError:GC overhead limit exceeded
GC回收时间过长时会抛出的OutOfMemory。过长是指,超过98%的时间都在用来做GC并且回收了不到2%的堆内存。连续多次的GC,都回收了不到2%的极端情况下才会抛出。假如不抛出GC overhead limit 错误会发生什么事情呢?那就是GC清理出来的一点内存很快又会被再次填满,强迫GC再次执行,这样造成恶性循环,CPU的使用率一直很高,但是GC没有任何的进展。
Demo:
/**
* 调整虚拟机的参数:
* -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m
*/
public class GCOverHeadDemo {
public static void main(String[] args) {
int i= 0;
List<String> list = new ArrayList<>();
while (true){
list.add(String.valueOf(++i).intern());
System.out.println(i);
}
}
}
//运行结果:
[Full GC (Ergonomics) [PSYoungGen: 1024K->1024K(2048K)] [ParOldGen: 7101K->7099K(7168K)] 8125K->8123K(9216K), [Metaspace: 3264K->3264K(1056768K)], 0.0296282 secs] [Times: user=0.08 sys=0.00, real=0.03 secs]
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
可以看到GC前后内存占用时一样的,也就是说虽然在频繁的GC,但是并没有起到什么作用,并没有回收回来多少的内存。
4、java.lang.OutOfMemoryError:Direct buffer memory
写NIO程序经常使用到ByteBuffer来读取或者写入数据,这是一种基于通道与缓冲区的I/O方式。它可以使用Native函数库直接分配堆外内存,然后通过一个存储在java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中提高性能,因为避免了java堆和Native堆中来回复制数据。
- ByteBuffer.allocate(capability) :这种方式是分配JVM堆内存,属于GC管辖范围之内。由于需要拷贝,所以速度相对较慢;
- ByteBuffer.allocateDirect(capability):这种方式是直接分配OS本地内存,不属于GC管辖范围之内,由于不需要内存拷贝所以速度相对较快。
但是如果不断分配本地内存,堆内存很少使用,那么JVM就不需要执行GC,DirectByteBuffer对象就不会被回收。这时候堆内存充足,但是本地内存已经用光了,再次尝试分配的时候就会出现OutOfMemoryError,那么程序就直接崩溃了。
Demo:
/**
* JVM配置参数:
* -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m
*/
public class DirectBufferMemoryDemo {
public static void main(String[] args) {
System.out.println("配置的MaxDirectMemorySize"+sun.misc.VM.maxDirectMemory()/(double)1024/1024+" MB");
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(6*1024*1024);
}
}
//运行结果:
配置的MaxDirectMemorySize5.0 MB
[GC (System.gc()) [PSYoungGen: 1785K->488K(2560K)] 1785K->728K(9728K), 0.0019042 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (System.gc()) [PSYoungGen: 488K->0K(2560K)] [ParOldGen: 240K->640K(7168K)] 728K->640K(9728K), [Metaspace: 3230K->3230K(1056768K)], 0.0077924 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
5、java.lang.OutOfMemoryError:unable to create new native thread
准确的说,这一个异常是和程序运行的平台相关的。导致的原因:
- 创建了太多的线程,一个应用创建多个线程,超过系统承载极限;
- 服务器不允许应用程序创建这么多的线程,Linux系统默认的允许单个进程可以创建的线程数量是1024个,当创建多 线程数量多于这个数字的时候就会抛出此异常
如何解决呢?
- 想办法减少应用程序创建的线程的数量,分析应用是否真的需要创建这么多的线程。如果不是,改变代码将线程数量降到最低;
- 对于有的应用,确实需要创建很多的线程,远超过Linux限制的1024个 限制,那么可以通过修改Linux服务器的配置,扩大Linux的默认限制。
Demo:
public class UnableCreateNewThreadDemo {
public static void main(String[] args) {
for (int i = 1; ;i++){
System.out.println("i = " +i);
new Thread(()->{
try {
Thread.sleep(Integer.MAX_VALUE);
} catch (InterruptedException e) {
e.printStackTrace();
}
},i+"").start();
}
}
}
//运行结果:
。。。。
i = 92916
i = 92917
Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
。。。。
6、java.lang.OutOfMemoryError:MetaSpace
元空间的本质和永久代类似,都是对JVM规范中的方法区的实现。不过元空间与永久代之间最大的区别在于:元空间不在虚拟机中,而是使用的本地内存。因此,默认情况下,元空间的大小仅仅受到本地内存的限制 。
元空间存放了以下的内容:
- 虚拟机加载的类信息;
- 常量池;
- 静态变量;
- 即时编译后的代码
模拟MetaSpace空间溢出,我们不断生成类往元空间里灌,类占据的空间总是会超过MetaSpace指定的空间大小的
查看元空间的大小:java -XX:+PrintFlagsInitial
Demo:
/**
* JVM参数配置:
* -XX:MetaSapceSize=5m
*/
public class MetaSpaceDemo {
public static void main(String[] args) {
for (int i = 0; i < 100_000_000; i++) {
generate("eu.plumbr.demo.Generated" + i);
}
}
public static Class generate(String name) throws Exception {
ClassPool pool = ClassPool.getDefault();
return pool.makeClass(name).toClass();
}
}