一般你去面试的时候,面试官经常会问:请谈谈你对OOM的认识?然后,你可能会说OOM就是out of memory,那如果你只是这么答的话,这可不是面试官想要的答案;面试官又接着问,那你生产过程中有遇到哪些OOM呢?请你说说出常见的OOM问题?这时的你可能是懵的。你知道几种常见的OOM呢?欢迎评论区留言。
常见的OOM总述:
1. StackOverflowError
线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常 。递归调用方法,如果没有方法出口的方法会造成StackOverflowError,或者说如果调用的过深都会抛出,这种错误也比较容易定位。
public class StackOverflowErrorDemo {
public static void main(String[] args){
stackOverflowError();
}
private static void stackOverflowError() {
stackOverflowError();
}
}
2. java.lang.OutOfMemoryError: Java heap space
溢出原因:深入理解Java虚拟机书中讲到java堆溢出,Java堆用于存储对象实例,只要不断地创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,那么在对象数量到达最大堆的容量限制后就会产生内存溢出异常。
import java.util.Random;
public class JavaHeapSpaceDemo {
public static void main(String[] args){
String str = "seu";
while(true){
str += str + new Random().nextInt(11111111)+new Random().nextInt(22222222);
str.intern();
}
}
}
3. java.lang.OutOfMemoryError:GC overhead limit exceeded
import java.util.ArrayList;
import java.util.List;
/*
* GC回收时间长时会抛出OutOfMemoryError。过长的定义是,超过98%的时间用来做GC并且回收了
* 不到2%的堆内存,连续多次GC都只回收了不到2%的极端情况下才会抛出。
假设不抛出GC overhead limit错误会发生什么情况呢?
那就是GC清理的这么点内存很快会再次填满,迫使GC再次执行,这样就形成恶性循环,CPU使用率一直
是100%,而GC缺没有任何成果。
* */
public class GCOverheadDemo {
public static void main(String[] args){
int i = 0;
List<String> list = new ArrayList<>();
try{
while(true){
list.add(String.valueOf(++i).intern());
}
}catch (Throwable e){
System.out.println("***************i:"+i);
e.printStackTrace();
throw e;
}
}
}
4. java.lang.OutOfMemoryError:Direct buffer memory
import java.nio.ByteBuffer;
/**
* 配置参数: -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m
* 导致原因:
* 写NIO程序经常使用ByteBuffer来读取或者写入数据,这是一种基于通道,缓冲区的IO方式
* ByteBuffer.alloction(capabicity)分配jvm堆内存,属于GC管辖范围
* ByteBuffer.allocateDirect(capabicity)分配本地内存,不属于GC管辖范围,由于不需要内存拷贝所以速度较快。
* 如果不断分配本地内存,但是堆内存很少使用,那么jvm不需要执行GC,DirectByteBuffer对象就不会被回收,这时候堆内存充足
* 但是本地内存已经使用光了,再次分配的时候就会出现OOM异常!
* --堆内存不够的时候,会触发fullGC顺带回收 DirectMemory!
* MaxDirectMemorySize 默认是64M
*/
public class DirectBufferMemoryDemo {
public static void main(String[] args) {
System.out.println("配置的MaxDirectMemory:"+(sun.misc.VM.maxDirectMemory()/(double)1024/1024)+"MB");
try{Thread.sleep(3000);} catch (InterruptedException e) { e.printStackTrace();}
//-XX:MaxDirectMemorySize=5m 我们配置为5M 但是实际使用是6M
ByteBuffer.allocateDirect(6*1024*1024);
}
}
5. java.lang.OutOfMemoryError:unable to create new native thread
不知道你们生产环境是否会出现这种情况,高并发请求服务器时,经常出现如下异常java.lang.OutOfMemoryError:unable to create new native thread。那出现的原因呢?
1.创建太多的线程了
2.服务器的设置限制了你创建线程的数量了(linux 允许创建的线程数是1024个)
6. java.langOutOfMemoryError:Metaspace
我们知道Java8及以后已经使用Metaspace来替代永久代,Metaspace并不在虚拟机内存中而是使用本地内存。主要还是是加载到内存中的 class 数量太多或者体积太大。这时可以通过JVM参数-XX:MaxMetaspaceSize指定。
* JVM参数: -XX:MetaspaceSize=8m -XX:MaxMetaspaceSize=8m
* Java8之后的版本使用Metaspace来替代永久代
* Metaspace是方法区在HotSpot中的实现,它与持久带最大的区别在于:Metaspace并不在虚拟机内存中而是使用
* 本地内存,也即在java8中,class metaspace(the virtual machines internal presentation of java class)
* ,被存储在叫做Metaspace的native memory
*
* 永久代(Metaspace)存放以下信息:
* 虚拟机加载的类信息
* 常量池
* 静态变量
* 即时编译后的代码
* */
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class MetaspaceOOMT {
static class OOMTest {
}
public static void main(String[] args) {
int i = 0;//模拟多少次后发生异常
try {
while (true) {
i++;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OOMTest.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
return methodProxy.invokeSuper(o, args);
}
});
enhancer.create();
}
} catch (Throwable e) {
System.out.println("********多少次后发生了异常:" + i);
e.printStackTrace();
}
}
}
总结
理解jvm才能更有深度的理解这些异常,对jvm的学习应该贯穿整个java!欢迎关注我的公众号,一起学习!
关注「Java源码进阶」,获取海量java,大数据,机器学习资料!