OutOfMemoryError(OOM)异常 2019-06-02 程序之旅 暂无评论 1054 次阅读 ## OutOfMemoryError(OOM)异常 面试和笔试时候问的最多的一道题,简单的说说其中的一些知识点。以下是我之前整理的jvm内存管理的PPT。 [JAVA虚拟机之内存管.pptx](https://mufeng-blog.oss-cn-beijing.aliyuncs.com/usr/uploads/2019/06/4291476880.pptx) ### 内存溢出的原因及其解决方法 在jvm虚拟机运行时,除了程序计数器外,其他的几个运行时区域都有可能会发生内存溢出的可能。 #### java堆溢出 因为不断创建对象,并且保证GC Roots到对象之间可达,导致垃圾回收机制不能清除这些对象,当不断创建的对象超过了最大堆容量限制后会产生内存溢出。 > - 常见的GC Root有: > - 通过System Class Loader或者Boot Class Loader加载的class对象,通过自定义类加载器加载的class不一定是GC Root > - 处于激活状态的线程 > - 栈中的对象 > - JNI栈中的对象 > - JNI中的全局对象 > - 正在被用于同步的各种锁对象 > - JVM自身持有的对象,比如系统类加载器等。 本次内存溢出测试使用的编辑器是IntelliJ IDEA,设置指定运行类的JVM堆内存。 ![1559436009118.png](https://mufeng-blog.oss-cn-beijing.aliyuncs.com/usr/uploads/2019/06/359373073.png) ![1559436079898.png](https://mufeng-blog.oss-cn-beijing.aliyuncs.com/usr/uploads/2019/06/1703060026.png) 当堆内存设置成一样的时候,其代码运行的java堆就不会自动扩展。便于内存溢出的代码测试,我将java堆设置成大小为10M,并对内存堆执行快照。 ``` -Xms10M -Xmx10M -XX:+HeapDumpOnOutOfMemoryError ``` 模拟内存溢出代码 ```java import java.util.ArrayList; import java.util.List; public class Main { static class OOMObject{ } public static void main(String[] args) { List list = new ArrayList<>(); while (true) { list.add(new OOMObject()); } } } ``` 运行后的结果为 ![1559436497214.png](https://mufeng-blog.oss-cn-beijing.aliyuncs.com/usr/uploads/2019/06/236114669.png) 程序运行后会在项目的根目录上创建一个dump文件,我这的文件为`java_pid14392.hprof`。 这里使用的内存映像分析工具有idea中的`JProfiler`和eclipse中的 `Memory Analyzer`,具体如何使用以后会有涉及这里就不多说。 ![1559441006250.png](https://mufeng-blog.oss-cn-beijing.aliyuncs.com/usr/uploads/2019/06/4163902635.png) ##### 内存溢出与内存泄漏 内存泄漏,当一个对象已经不再需要使用,jvm垃圾回收应该把该对象进行回收,此时另一个正在使用的对象持有它的引用,从而导致不需要使用的对象无法进行回收,导致需要被回收的对象依旧停留在内存当中,这就产生了内存泄漏。 内存溢出与内存泄漏的关系。内存泄漏是造成内存溢出的主要原因之一,当程序产生过多的对象,但不能及时的进行回收(内存泄漏),这样会导致jvm内存使用超过内存所分配的额度,从而导致了内存溢出。 可以通过内存映像工具分析得到,其占内存过大的对象, 并找到其GC Roots的引用链,从而定位出内存泄漏代码位置,对代码进行优化。 ### 虚拟机栈和本地方法栈溢出 在HotSpot虚拟机中,可以通过`-Xss`参数进行栈容量设定。关于虚拟机栈中常见的两种异常为: - 请求栈帧时超过虚拟机栈中最大深度,抛出StackOverFlowError异常。 - 请求栈帧时,无法申请更多的内存,则抛出OutOfMemoryError异常。 大多数情况下虚拟机栈只会发生StackOverFlowError异常,这里测试的是OutOfMemoryError异常,车市代码如下: > 测试前最好保存当前工作文件,运行该程序可能会导致电脑死机 ```java /** * 通过创建大量的线程来触发内存泄漏 */ public class JVMStackOOM { private void dontStop(){ while (true){ } } public void stackLeakByThread() { while (true) { Thread thread = new Thread(new Runnable() { @Override public void run() { dontStop(); } }); thread.start(); } } public static void main(String[] args) { JVMStackOOM oom = new JVMStackOOM(); oom.stackLeakByThread(); } } ``` 测试代码栈深可以使用如下代码测试 ```java /** * 通过递归测试栈深度 */ public class StackSOF { private int stackLength = 1; public void stackLeack(){ stackLength ++; stackLeack(); } public static void main(String[] args) { StackSOF stackSOF = new StackSOF(); try { stackSOF.stackLeack(); } catch (Throwable e) { System.out.println("深度为:" + stackSOF.stackLength); throw e; } } } ``` ![1559445948053.png](https://mufeng-blog.oss-cn-beijing.aliyuncs.com/usr/uploads/2019/06/1114710016.png) ### 方法区和运行时常量池溢出 先了解常量池在不同版本的jdk中的位置。 在jdk1.6下,运行时常量池是存在于方法区内,但到了jdk1.7后,运行时常量池被转移到了java堆中。之后到了jdk1.8后这个方法区被移除,取而代之的是元数据空间,与方法区不同的是,元数据空间是在运行时数据区域之外,从本地内存单独分配的一个区域。 打赏: 微信, 支付宝 标签: java, oom, jvm 本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。