【JVM】浅谈对OOM的认识
一张图区分Exception和Error
1、java.lang.*Error
这是栈溢出错误,如果一个线程所需用到栈的大小>配置允许最大的栈大小,那么jvm就会抛出*。
一般出现这个问题是因为程序里有死循环或递归调用所产生的。由于深度递归,抛出此错误以指示应用程序的堆栈已耗尽。
【举例】
2、java.lang.OutOfMemoryError:Java heap space
使用Java程序从数据库中查询大量的数据时出现该异常。
【举例】
3、java.lang.OutOfMemoryError:GC overhead limit exceeded
GC回收时间 过长 时会抛出OutOfMemoryError。“过长” 的定义是,超过98%的时间用来做GC并且回收了不到2%的堆内存,连续多次GC 都只回收了不到2%的极端情况下才会抛出。加入不抛出 GC overhead limit 错误,会发生什么情况嗯?
那就是GC清理的这么点内存很快会再次填满,迫使GC再次执行,这样就形成恶性循环。CPU使用率一直是100%,而GC却没有任何成果。
【举例】
4、lava.lang.OutOfMemoryError:Direct buffer memory(直接内存溢出)
配置参数:-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m
故障现象:Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
导致原因:写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,那程序就直接崩溃了。
【举例】
5、java.lang.OutOfMemoryError:unable to create new native thread(不能再创建更多新的线程了)
高并发请求服务器时,经常出现如下异常:java.lang.OutOfMemoryError:unable to create new native thread。
准却讲,该native thread异常与对应的平台有关。
导致原因:
①你的应用创建了太多线程了,一个应用进程创建多个线程,超过系统承载极限
②你的服务器并不允许你的应用程序船舰这么多线程,Linux系统默认允许单个进程可以创建的线程数是1024个,你的应用创建超过这个数量,就会报java.lang.OutOfMemoryError:unable to create new native thread
解决办法:
①想办法降低你应用程序创建线程的数量,分析应用是否真的需要创建这么多线程,如果不是,改代码将线程数降到最低
②对于有的应用,确实需要创建很多线程,远超过Linux系统的默认1024个线程的限制,可以通过修改Linux服务器配置,扩大Linux默认限制
6、java.lang.OutOfMemoryError:Metaspace
JDK1.7中,存储在永久代的部分数据就已经转移到了Java Heap或者是 Native Heap,譬如符号引用(Symbols)转移到了native heap;字面量(interned strings)转移到了java heap;类的静态变量(class statics)转移到了java heap。但永久代仍存在于JDK1.7中,并没完全移除。
JDK 8.HotSpot JVM使用本地化的内存存放类的元数据,这个空间叫做元空间(Metaspace)。
官方定义:“In JDK 8, classes metadata is now stored in the native heap and this space is called Metaspace”。元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制,但可以通过以下参数来指定元空间的大小:-XX:MetaspaceSize、-XX:MaxMetaspaceSize
。
public class MetaspaceOutOfMemory {
public static void main(String[] args) throws Exception {
ClassPool cp = ClassPool.getDefault();
for (int i = 0;; i++) {
cp.makeClass("com.demo.MetaspaceClass" + i).toClass();
Thread.sleep(1);
}
}
}
该错误的主要原因, 是加载到内存中的 class 数量太多或者体积太大。
上一篇: JVM之深入理解虚拟机栈(一)
下一篇: jvm内存区域之虚拟机栈