JVM笔记三:Java内存区域与内存溢出异常——OutOfMemoryError异常

文章目录
  1. 1. Java堆溢出
    1. 1.1. 虚拟机栈和本地方法栈溢出
    2. 1.2. 方法区和运行时常量池溢出

Java堆溢出

Java堆用于存储对象实例,只要不断地创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,那么在对象数量到达最大的容量限制后就会产生内存溢出异常。

下面代码限制Java堆的大小为20MB,不可扩展(将堆的最小值-Xms参数与最大值-Xmx参数设置为一样即可避免堆自动扩展),通过参数-XX:+HeapDumpOnOutOfMemoryError可以让虚拟机在出现内存溢出时Dump出当前的内存堆转储快照以便事后进行分析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* HeapOOM
* Created by larry.su on 2017/2/7.
* -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
*/

public class HeapOOM {
static class OOMObject {}

public static void main(String[] args) {
List<OOMObject> list = new ArrayList<OOMObject>();
while (true) {
list.add(new OOMObject());
}
}
}

运行结果:

1
2
3
4
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid65766.hprof ...
Heap dump file created [27784157 bytes in 0.137 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

虚拟机栈和本地方法栈溢出

HotSpot虚拟机中并不区分虚拟机栈和本地方法栈,对于HotSpot来说,虽然-Xoss参数(设置本地方法栈大小)存在,但实际上是无效的。栈容量只由-Xss参数设定。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* VM Args: -Xss160k
* Created by larry.su on 2017/2/17.
*/

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("stackLength:" + oom.stackLength);
throw e;
}
}
}

运行结果:

1
2
3
4
5
Exception in thread "main" java.lang.StackOverflowError
stackLength:744
at com.jvm.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:14)
at com.jvm.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:15)
at com.jvm.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:15)

在单线程下,无论是由于栈帧太大还是虚拟机栈帧容量太小,当内存无法分配的时候,虚拟机抛出的都是StackOverFlowError异常。

方法区和运行时常量池溢出

由于运行时常量池是方法区的一部分,因此这两个区域的溢出测试就放在一起进行。方法区用于存放Class的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。

String.intern()。在JDK1.6之前版本中由于常量池分配在永久内存中,我们可以通过-XX:PermSize和-XX:MaxPermSize限制方法区的大小,从而间接限制其中常量池的容量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* Returns a canonical representation for the string object.
* <p>
* A pool of strings, initially empty, is maintained privately by the
* class {@code String}.
* <p>
* When the intern method is invoked, if the pool already contains a
* string equal to this {@code String} object as determined by
* the {@link #equals(Object)} method, then the string from the pool is
* returned. Otherwise, this {@code String} object is added to the
* pool and a reference to this {@code String} object is returned.
* <p>
* It follows that for any two strings {@code s} and {@code t},
* {@code s.intern() == t.intern()} is {@code true}
* if and only if {@code s.equals(t)} is {@code true}.
* <p>
* All literal strings and string-valued constant expressions are
* interned. String literals are defined in section 3.10.5 of the
* <cite>The Java&trade; Language Specification</cite>.
*
* @return a string that has the same contents as this string, but is
* guaranteed to be from a pool of unique strings.
*/

public native String intern();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* VM Args: -XX:PermSize=10M -XX:MaxPermSize=10M
* Created by larry.su on 2017/2/17.
*/

public class RuntimeConstantsPoolOOM {
public static void main(String[] args) throws Throwable {
//使用List保持常量池的引用,避免Full GC回收常量池行为
List<String> list = new ArrayList<String>();
//10M 的PermSize在Integer范围内足够产生OOM
int i = 0;
while (true) {
try {
list.add(String.valueOf(i++).intern());
} catch (Throwable e) {
System.out.println(i);
throw e;
}
}
}
}

运行结果:

1
2
3
4
86889
Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
at java.lang.String.intern(Native Method)
at com.jvm.RuntimeConstantsPoolOOM.main(RuntimeConstantsPoolOOM.java:21)

在JDK1.7中运行这段代码就不会得到相同的接口,while会一直循环下去。

在JDK8中,while同样一直循环下去,同时控制台打印如下信息:

1
2
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option PermSize=10M; support was removed in 8.0
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=10M; support was removed in 8.0