在Java虚拟机规范中,除了程序计数器外,虚拟机内存的其他几个运行时区域都有发生
OutOfMemoryError
异常的可能。
一个小插曲:面试的时候被问到为什么程序计数器不会发生OOM异常?
答案:Java程序计数器记录指令的偏移地址,这个地址的值其范围是可知晓的,所以在程序计数器建立之初就能分配一个绝对不会溢出的内存。
下面是一些常见的异常及解决方法:
1. Java堆溢出
Java堆用于存储对象实例,只要不断的创建对象,并保证GC Root到对象之间有可达的路径即可避免垃圾回收机制清除那些对象,那么当对象数量达到最大堆的容量限制后就会产生内存超出异常。
/**
* 测试堆内存溢出
* 参数为:-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
* @author
*
*/
public class HeapOOM {
public static void main(String[] args) {
String name = "xingze";
List<String> list = new ArrayList<>();
while(true) {
list.add(name);
}
}
}
运行结果:
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid2216.hprof ...
Heap dump file created [15858972 bytes in 0.091 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3210)
at java.util.Arrays.copyOf(Arrays.java:3181)
at java.util.ArrayList.grow(ArrayList.java:265)
at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:239)
at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:231)
at java.util.ArrayList.add(ArrayList.java:462)
at ErrorAndException.HeapOOM.main(HeapOOM.java:18)
分析:Java堆内存溢出时,异常堆栈信息java.lang.OutOfMemoryError
后会进一步提示Java heap space
解决办法:
先通过内存映像分析工具(如Eclipse Memory Analyzer)对Dump出来的堆转储快照进行分析,重点是确认内存中的对象是否是必要的,也就是先分清楚是出现了内存泄漏(Memory Leak)还是内存溢出(Memory Overflow)。
如果是内存泄漏,可进异一步通过工具查看泄漏对象到GC Roots的引用链。就可以比较准确的定位泄漏代码的位置。
如果不存在泄漏,也就是说,内存中的对象确实都还必须存活着,那就应当检查虚拟机的堆参数(-Xms与-Xmx),与物理机内存对比看是否还能增大;代码上,看是否存在一些对象生命周期过长的情况,尝试减少程序运行期间的内存消耗。
2. 虚拟机栈和本地方法栈溢出
栈容量只由-Xss参数设定,虚拟机栈和本地方法栈存在两种异常:
StackOverflowError
(栈溢出错误)异常:当线程请求的栈深度大于虚拟机所允许的最大深度时(无限递归),会抛出StackOverflowError异常。OutOfMemoryError
异常:当虚拟机在扩展栈时无法申请到足够的内存空间时,会抛出OutOfMemoryError异常。
public class StackOverflowException01 {
int stackLength = 1;
void addPlusPlus() {
this.stackLength++;
this.addPlusPlus();
}
public static void main(String[] args) throws Throwable{
StackOverflowException01 oom = new StackOverflowException01();
try {
oom.addPlusPlus();
}catch(Throwable e) {
System.out.println("stack length :"+ oom.stackLength);
throw e;
}
}
}
运行结果:
stack length :99098
Exception in thread "main" java.lang.StackOverflowError
at ErrorAndException.StackOverflowException01.addPlusPlus(StackOverflowException01.java:14)
at ErrorAndException.StackOverflowException01.addPlusPlus(StackOverflowException01.java:14)
at ErrorAndException.StackOverflowException01.addPlusPlus(StackOverflowException01.java:14)
......
分析:在单个线程下,无论是由于栈帧太大还是虚拟机栈容量太小,当内存无法分配时,虚拟机抛出的都是StackOverflowError异常。
解决办法:Java虚拟机栈大小 = 每个进程的内存大小 - JVM堆大小 - JVM方法区大小 - 程序计数器大小
;
- 减少线程数
- 更换64位虚拟机
- 减少最大堆和减少栈容量来换取更多的内存
3. 方法区和运行时常量池溢出
运行时常量池是方法区的一部分。运行时常量区溢出时,在OutOfMemoryError
后面的提示信息是PerGen space
,说明运行时常量池属于方法区的一部分。