JVM(四)——OutOfMemoryError 异常
在 Java 运行时的数据区域 中我已经知道了抛出 OutOfMemoryError 异常的区域在 虚拟机栈、本地方法栈、java堆和方法区几个区域。
Java 堆溢出
Java 堆是创建对象的区域,当GC来不及清除这些对象,并且对象数量达到最大堆的容量限制后就会产生内存溢出异常。
Java 堆可以自动扩展,如果将 -Xms(堆最小值)和 -Xmx(堆最大值)设置为一样可以避免堆自动扩展。
Java 堆内存的 OOM 异常是实际应用中常见的内存溢出情况。当 Java 堆出现 OOM 异常时,会提示错误信息:
java.lang.OutOfMemoryError: Java heap sapce复制代码
值得注意的是,此时异常还分为两种:
- 内存泄露(Memroy Leak)
对象本应该被 GC 回收,而未被回收。内存从 GC 中泄露出来。 - 内存溢出(Memory Overflow)
对象都还必须存活(JVM会判断),这些对象占的内存超出了Java堆的最大容量。此时最直接的方法就是调大物理内存。
两者的唯一区别就是,GC没有清除的对象是否是必要的存在的。
虚拟机栈
由于在HotSpot虚拟机中并不区分虚拟机栈和本地方法栈,所以,-Xoss参数(本地方法栈大小)没有是实际作用。栈容量只由 -Xss 参数设定。
在虚拟机栈中,存在两种异常:
- SatckOverflowError
线程请求的站深度大于虚拟机栈所允许的最大深度 - OutOfMemoryError
虚拟机在扩展时无法申请到足够的内存空间(同java堆)
当在单线程环境中,只会出现 StackOverflowError 异常。切换至多线程后,会出现 OutOfMemoryError 异常。
看起来,可以通过增大内存空间来缓解或者解决虚拟机栈的 OOM,但是,通过 Java 运行时的数据区域 分析得知:
物理机总内存 = 程序计数器消耗内存 + Xmx(最大 Java堆容量)+ MaxPermSize(最大方法区容量)+ Xss(虚拟机栈容量)+ Xoss(本地栈容量)复制代码
由于程序计数器消耗内存很小,忽略不计。并且将本地栈容量归为虚拟机栈容量管理的话,那么:
Xss(虚拟机栈容量)= 物理机总内存 - Xmx(最大 Java堆容量)- MaxPermSize(最大方法区容量)复制代码
虚拟机栈是线程的“私有内存”,目的是线程分配运行时需要的内存容量,所以有:
Xss(虚拟机栈容量)= 线程数 * 线程栈容量复制代码
最后得到:
线程数 * 线程栈容量 = 物理机总内存 - Xmx(最大 Java堆容量)- MaxPermSize(最大方法区容量)复制代码
在实际情况中,我们希望得到更高的并发量,也就是增大线程数量,此时,有三种方法:
- 增大物理机总内存
- 减小java堆内存、方法区容量
- 减小线程栈容量
作为开发人员,能做的,也就是后两者。第一条不说申请流程复杂,就算是增加了,那也可能是个无底洞。