这个面试题是一个朋友在面试的时候碰到的,什么时候会抛出OutOfMemery异常呢?初看好像挺简单的,其实深究起来考察的是对整个JVM的了解,而且这个问题从网上可以翻到一些乱七八糟的答案,其实在总结下来基本上4个场景可以概括下来。堆内存溢出 堆内存溢出太常见,大部分人都应该能想得到这一点,堆内存用来存储对象实例,我们只要不停的创建对象,并且保证GCRoots和对象之间有可达路径避免垃圾回收,那么在对象数量超过最大堆的大小限制后很快就能出现这个异常。 写一段代码测试一下,设置堆内存大小2M。 publicclassHeapOOM{publicstaticvoidmain(String〔〕args){ListHeapOOMlistnewArrayList();while(true){list。add(newHeapOOM());}}} 运行代码,很快能看见OOM异常出现,这里的提示是Javaheapspace堆内存溢出。 一般的排查方式可以通过设置XX:HeapDumpOnOutOfMemoryError在发生异常时dump出当前的内存转储快照来分析,分析可以使用EclipseMemoryAnalyzer(MAT)来分析,独立文件可以在官网下载。 另外如果使用的是IDEA的话,可以使用商业版JProfiler或者开源版本的JVMProfiler,此外IDEA2018版本之后内置了分析工具,包括FlameGraph(火焰图)和CallTree(调用树)功能。 火焰图 方法区(运行时常量池)和元空间溢出 方法区和堆一样,是线程共享的区域,包含Class文件信息、运行时常量池、常量池,运行时常量池和常量池的主要区别是具备动态性,也就是不一定非要是在Class文件中的常量池中的内容才能进入运行时常量池,运行期间也可以可以将新的常量放入池中,比如String的intern()方法。 我们写一段代码验证一下String。intern(),同时我们设置XX:MetaspaceSize50mXX:MaxMetaspaceSize50m元空间大小。由于我使用的是1。8版本的JDK,而1。8版本之前方法区存在于永久代(PermGen),1。8之后取消了永久代的概念,转为元空间(Metaspace),如果是之前版本可以设置PermSizeMaxPermSize永久代的大小。privatestaticSpublicstaticvoidmain(String〔〕args){ListStringlistnewArrayList();while(true){Stringstr2strstr2;list。add(str。intern());}} 运行代码,会发现代码报错。 再次修改配置,去除元空间限制,修改堆内存大小Xms20mXmx20m,可以看见堆内存报错。 这是为什么呢?intern()本身是一个native方法,它的作用是:如果字符串常量池中已经包含一个等于此String对象的字符串,则返回代表池中这个字符串的String对象;否则,将此String对象包含的字符串添加到常量池中,并且返回String对象的引用。 而在1。7版本之后,字符串常量池已经转移到堆区,所以会报出堆内存溢出的错误,如果1。7之前版本的话会看见PermGenspace的报错。直接内存溢出 直接内存并不是虚拟机运行时数据区域的一部分,并且不受堆内存的限制,但是受到机器内存大小的限制。常见的比如在NIO中可以使用native函数直接分配堆外内存就容易导致OOM的问题。 直接内存大小可以通过XX:MaxDirectMemorySize指定,如果不指定,则默认与Java堆最大值Xmx一样。 由直接内存导致的内存溢出,一个明显的特征是在Dump文件中不会看见明显的异常,如果发现OOM之后Dump文件很小,而程序中又直接或间接使用了NIO,那就可以考虑检查一下是不是这方面的原因。栈内存溢出 栈是线程私有,它的生命周期和线程相同。每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息,方法调用的过程就是栈帧入栈和出栈的过程。 在java虚拟机规范中,对虚拟机栈定义了两种异常:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常如果虚拟机栈可以动态扩展,并且扩展时无法申请到足够的内存,抛出OutOfMemoryError异常 先写一段代码测试一下,设置Xss160k,Xss代表每个线程的栈内存大小publicclassStackOOM{privateintlength1;publicvoidstackTest(){System。out。println(stacklenghtlength);stackTest();}publicstaticvoidmain(String〔〕args){StackOOMtestnewStackOOM();test。stackTest();}} 测试发现,单线程下无论怎么设置参数都是StackOverflow异常。 尝试把代码修改为多线程,调整Xss2m,因为为每个线程分配的内存越大,栈空间可容纳的线程数量越少,越容易产生内存溢出。反之,如果内存不够的情况,可以调小该参数来达到支撑更多线程的目的。publicclassStackOOM{privatevoiddontStop(){while(true){}}publicvoidstackLeakByThread(){while(true){newThread(()dontStop())。start();}}publicstaticvoidmain(String〔〕args)throwsThrowable{StackOOMstackOOMnewStackOOM();stackOOM。stackLeakByThread();}} 私信666获取更多硬核面试资料 END