【0】README
0.1)本文转自 深入理解 jvm, 旨在学习 OutOfMemoryError 异常(内存溢出异常) 的触发类型;
0)准备知识
0.1)除了程序计数器外,虚拟机内存的其他几个运行时区域(方法区+虚拟机栈+本地方法栈+java堆)都有发生 OutOfMemoryError异常的可能性;0.2)如何设置Eclipse 的 VM 参数?
1)java堆内存溢出异常测试
1.1)运行结果(运行参数 -Xms20M -Xmx20M -XX:+HeapDumpOnOutOfMemoryError)
package com.jvm.chapter2;import java.util.ArrayList; import java.util.List;/*** VM Args:-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError* @author zzm*/ public class HeapOOM {static class OOMObject {}public static void main(String[] args) {List<OOMObject> list = new ArrayList<OOMObject>();while (true) {list.add(new OOMObject());}} }<strong> </strong>
1.2)solution: 通过内存映像分析工具(如 Eclipse Memory Analyzer) 对 dump(转储) 出来的堆转储快照进行分析,重点是确认内存中的对象是否是有必要的,也就是要先分清楚是出现了内存泄露(Memory Leak) 还是内存溢出(Memory Overflow);
2)虚拟机栈和本地方法栈溢出
2.1) HotSpot 虚拟机中并不区分虚拟机栈和本地方法栈;2.2)使用 -Xss 参数减少栈内存容量。结果: 抛出StackOverflowError 异常,异常出现时输出的堆栈深度相应缩小;2.3)运行结果:(运行参数 -Xss128k)package com.jvm.chapter2;/*** VM Args:-Xss128k* @author zzm*/ public class JavaVMStackSOF {private int stackLength = 1;public void stackLeak() {stackLength++;stackLeak();}public static void main(String[] args) throws Throwable {JavaVMStackSOF oom = new JavaVMStackSOF();try {oom.stackLeak();} catch (Throwable e) {System.out.println("stack length:" + oom.stackLength);throw e;}} }
2.4)定义了大量的本地变量,增大此方法帧中本地变量表的长度。结果: 抛出StackOverflowError 异常时输出的堆栈深度相应缩小;
3)方法区和运行时常量池溢出
3.0)String.intern() :是一个Native方法,它的作用是: 如果字符串常量池中已经包含一个等于此String 对象的字符串,则返回代表池中这个字符串的String对象;否则,将此String对象包含的字符串添加到常量池中,并且返回此String 对象 的引用;3.1)由于常量池分配在永久代内,通过 -XX:PermSize 和 -XX:MaxPermSize 限制方法区大小,从而间接限制其中常量池的容量;3.2)运行参数(-XX:PermSize=10M -XX:MaxPermSize=10M)Attention)没有抛出异常,因为
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option PermSize=1M; support was removed in 8.0Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=1M; support was removed in 8.0
3.3)测试 String.intern方法:package com.jvm.chapter2;public class RuntimeConstantPoolOOM {public static void main(String[] args){String str1 = new StringBuilder("中国").append("钓鱼岛").toString();System.out.println(str1.intern() == str1);// trueString str2 = new StringBuilder("ja").append("va").toString();System.out.println(str2.intern() == str2);// false} }
对上图的分析)Analysis:
A1)在jdk 1.6中,会得到两个false。 因为在 jdk1.6中,intern 方法会把首次遇到的字符串实例copy 到永久代中,返回的也是永久代中这个字符串市实例的引用,而StringBuilder 创建的字符串实例在 java堆上,所以必然不是同一个引用;A2)在jdk1.7中,会得到一个true和一个false。因为在 jdk1.7中的 intern 方法不会copy实例,只是在常量池中记录首次出现的实例引用,因此intern() 方法的引用和由StringBuilder 创建的那个字符串实例是同一个。对str2 比较返回false 是因为“java”这个字符串在执行 StringBuilder.toString()之前已经出现过了,字符串常量池已经有它的引用了,不符合“首次出现原则”,而“计算机软件”这个字符串则是首次出现,因此返回true;
4)本机直接内存溢出
4.1)DirectMemory容量: 可以通过-XX:MaxDirectMemorySize 指定,如果不指定,则默认与java 堆最大值(-Xmx指定)一样;