目录
Java堆溢出
虚拟机栈和本地方法栈溢出
方法区和运行时常量池溢出
直接内存溢出
Java堆中存储的是实例对象,只要不停的创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清楚这些对象,随着对象数量的增加,超过堆的最大容量就会产生内存溢出异常。
虚拟机参数为:-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
//-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
public class HeapOOM {
static class MyObject{
}
public static void main(String[] args) {
List list = new ArrayList<>();
while (true){
list.add(new MyObject());
}
}
}
运行结果:
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid35516.hprof ...
Heap dump file created [27754456 bytes in 0.085 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 HeapOOM.main(HeapOOM.java:21)
HotSpot虚拟机中并不区分虚拟机栈和本地方法栈,因此-Xoss(本地方法栈容量)虽然存在,但实际上是没有效果的,栈容量只能由
-Xss(是指设定每个线程的堆栈大小)参数来设置,栈溢出分为两种情况
①如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowErrory异常。
②如果虚拟机的栈内存允许动态扩展,当扩展栈容量无法申请到足够的内存时,将抛出OutOtMemoryError异常 。
验证上述两种情况,
①可以使用 -Xss参数减少栈内存容量
结果:抛出StackOverflowError异常,异常出现时输出堆栈深度响应缩小
②定义了大量的本地变量,增大此方法珍重本地变量表长度。
结果:抛出StackOverflowError异常,异常出现时输出堆栈深度响应缩小
//-Xss256k
public class JavaVMStackSOF {
private int stackLength = 1;
public void stackLeak(){
stackLength++;
stackLeak();
}
public static void main(String[] args) {
JavaVMStackSOF stackSOF = new JavaVMStackSOF();
try {
stackSOF.stackLeak();
}catch (Throwable e){
System.out.println("length:"+stackSOF.stackLength);
throw e;
}
}
}
运行结果:
length:1889
Exception in thread "main" java.lang.StackOverflowError
at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:12)
at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:13)
//-Xss128k
运行结果:
Error: Could not create the Java Virtual Machine.
The stack size specified is too small, Specify at least 160k
Error: A fatal exception has occurred. Program will exit.
public class JavaVMStackSOF02 {
private static int stackLength = 0;
public static void test(){
long unused01,unused11,unused21,unused31,unused41,unused51,unused61,unused71,unused81,unused91,
unused02,unused12,unused22,unused32,unused42,unused52,unused62,unused72,unused82,unused92,
unused03,unused13,unused23,unused33,unused43,unused53,unused63,unused73,unused83,unused93,
unused04,unused14,unused24,unused34,unused44,unused54,unused64,unused74,unused84,unused94,
unused05,unused15,unused25,unused35,unused45,unused55,unused65,unused75,unused85,unused95,
unused06,unused16,unused26,unused36,unused46,unused56,unused66,unused76,unused86,unused96,
unused07,unused17,unused27,unused37,unused47,unused57,unused67,unused77,unused87,unused97,
unused08,unused18,unused28,unused38,unused48,unused58,unused68,unused78,unused88,unused98,
unused09,unused19,unused29,unused39,unused49,unused59,unused69,unused79,unused89,unused99;
stackLength++;
test();
unused01=unused11=unused21=unused31=unused41=unused51=unused61=unused71=unused81=unused91=
unused02=unused12=unused22=unused32=unused42=unused52=unused62=unused72=unused82=unused92=
unused03=unused13=unused23=unused33=unused43=unused53=unused63=unused73=unused83=unused93=
unused04=unused14=unused24=unused34=unused44=unused54=unused64=unused74=unused84=unused94=
unused05=unused15=unused25=unused35=unused45=unused55=unused65=unused75=unused85=unused95=
unused06=unused16=unused26=unused36=unused46=unused56=unused66=unused76=unused86=unused96=
unused07=unused17=unused27=unused37=unused47=unused57=unused67=unused77=unused87=unused97=
unused08=unused18=unused28=unused38=unused48=unused58=unused68=unused78=unused88=unused98=
unused09=unused19=unused29=unused39=unused49=unused59=unused69=unused79=unused89=unused99=0;
}
public static void main(String[] args) {
JavaVMStackSOF02 stackSOF = new JavaVMStackSOF02();
try {
test();
}catch (Throwable e){
System.out.println("length:"+stackSOF.stackLength);
throw e;
}
}
}
运行结果:
length:58
Exception in thread "main" java.lang.StackOverflowError
at JavaVMStackSOF02.test(JavaVMStackSOF02.java:21)
at JavaVMStackSOF02.test(JavaVMStackSOF02.java:22)
at JavaVMStackSOF02.test(JavaVMStackSOF02.java:22)
结果表明:无论是由于栈桢太大还是虚拟机栈容量太小,当新的栈桢内存无法分配的时候,HotSpot虚拟机都会抛出StackOverflowError异常。
public class JavaVMStackOOM {
private void dontStop(){
while (true){
}
}
public void stackLeakByThread(){
while (true){
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
dontStop();
}
});
thread.start();
}
}
public static void main(String[] args) throws Throwable{
JavaVMStackOOM oom = new JavaVMStackOOM();
oom.stackLeakByThread();
}
}
运行结果:
OOM
操作系统给每个进行分配的内存是有限的,虚拟机 最大内存减去堆内存、方法区内存剩下的就java中虚拟机栈内存和本地方法栈内存,线程创建时会分配虚拟机栈和本地方栈和程序计数器,因此每个线程分配到的栈内存越大,则能创建的越少。
方法区溢出:方法区的主要职责是用于存放类型和相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。可以通过运行时大量的产生类去填充方法区的方式来验证溢出。
字符串常量池溢出:Java7以后字符串常量池被移至Java堆中
--Xmx6m
public class RuntimeConstantPoolOOM {
public static void main(String[] args) {
Set set = new HashSet();
short i = 0;
while (true){
set.add(String.valueOf(i++).intern());
}
}
}
运行结果:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.HashMap.resize(HashMap.java:704)
at java.util.HashMap.putVal(HashMap.java:663)
at java.util.HashMap.put(HashMap.java:612)
at java.util.HashSet.add(HashSet.java:220)
String.intern()首次 原则:如果字符串常量字符串常量池中,则调用String.intern()返回结果,如果不在,看堆中有没有,没有则在字符串常量池中创建一份,有则返回堆的地址。
直接内存的容量大小可通过-XX:MaxDirectMemorySize参数来指定,如果不指定就默认与Java堆最大值一致。
下面是使用Unsafe类模拟直接内存异常。
//-Xmx20m -XX:MaxDirectMemorySize=10M
public class DirectMemoryOOM {
private static final int _1MB = 1024*1024;
public static void main(String[] args) throws Exception{
Field unsafeField = Unsafe.class.getDeclaredFields()[0];
unsafeField.setAccessible(true);
Unsafe unsafe= (Unsafe)unsafeField.get(null);
int index = 0;
while (true){
unsafe.allocateMemory(_1MB);
System.out.println(index++);
}
}
}
运行结果:
Java8下未发生OOM