首先,JVM除了程序计数器之外,都可能发生内存溢出OutOfMemoryError(OOM)异常。这里主要对可能发生内存溢出的区域,原因进行总结。
1.JAVA虚拟机栈
虚拟机栈是线程私有的,虚拟机栈主要存储局部变量。Java虚拟机规范中,规定了此区域会抛出两种异常:
(1)如果请求栈深度大于虚拟机允许的深度,即涉及到方法层级调用太多,超过一定限度,将抛出*Error异常;这里说的栈的深度主要是java启动参数中xss参数,虚拟机栈大小配置;
(2)虚拟机栈动态扩展,如果得不到足够的内存申请空间,就会抛出OOM异常;虚拟机栈动态扩展,即当栈空间不够的时候,会自动加大栈的空间,避免*Error,此时申请空间不足,便会OOM,部分虚拟机具备这个功能;
2.本地方法栈
本地方法栈是为Native方法方法服务的,与虚拟机栈一样,本地方法栈也会抛出OOM以及*Error异常;
3.JAVA堆
JAVA堆是内存管理最大的一块,是所有线程共享的一块区域,在虚拟机启动的时候创建,主要存储对象实例。这块主要通过启动参数-Xmx进行配置,如果申请的对象实例大小超过该配置的参数,便出现OOM异常。内存泄漏也会导致该区域的OOM,内存泄漏会导致不能回收的对象停留在堆中,随着时间推移便会消耗完堆空间出发OOM。
4.方法区
方法区和java堆一样,是各个线程共享的内存区域,用于存储已被虚拟机加载的类信息,常量、静态变量和即时编译器编译后的代码数据。当方法区无法满足内存分配,将抛出OOM异常。
当前的一些框架,如Spring、Hibernate等,会使用CGlib技术对类进行增强,相应地会增加类的大小;
还有一些应用,会动态生成JSP文件,JSP文件是需要编译成Class文件的,大量的文件也有溢出的可能;
或者开发代码中往常量池添加过多的常量,也有可能造成常量池溢出。
另外一种可能就是我们的应用本身的类就太多,而方法区设置的容量不足,也会容易溢出。
设置方法区的大小,可通过配置-XX:PremSize 设置最小值,-XX:MaxPremSize设置最大值。
5.直接内存
直接内存(Direct Memory)又称堆外内存,内存对象分配在Java虚拟机的堆以外的内存,这些内存直接受操作系统管理(而不是虚拟机);
(1)减少了垃圾回收
使用堆外内存的话,堆外内存是直接受操作系统管理( 而不是虚拟机 )。这样做的结果就是能保持一个较小的堆内内存,以减少垃圾收集对应用的影响。
(2)提升复制速度(io效率)
堆内内存由JVM管理,属于“用户态”;而堆外内存由OS管理,属于“内核态”。如果从堆内向磁盘写数据时,数据会被先复制到堆外内存,即内核缓冲区,然后再由OS写入磁盘,使用堆外内存避免了这个操作。
如果堆外内存申请超过,物理内存的限制也会出现OOM异常。