关于虚拟机栈和本地方法栈,在Java虚拟机规范中描述了两种异常:
- 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常。
- 如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常。
在实验中,如果将实验范围限制于单线程中的操作,尝试了下面两种方法均无法让虚拟机产生OutOfMemoryError异常,尝试的结果都是获得StackOverflowError异常。
- 使用-Xss参数减少栈内存容量,结果:抛出StackOverflowError异常,异常出现时输出的栈深度相应缩小。
首先设置栈容量参数为-Xss128K
代码:
package jvm;
/**
* -Xss128K
*
* @author Poison
*
*/
public class JavaVMStackSOF {
private int stackLength = 1;
public void stackLeak() {
stackLength++;
stackLeak();
}
public static void main(String[] args) {
JavaVMStackSOF oom = new JavaVMStackSOF();
try {
oom.stackLeak();
} catch (Throwable e) {
System.out.println("stack length:" + oom.stackLength);
throw e;
}
}
}
运行结果:
stack length:995
Exception in thread “main” java.lang.StackOverflowError
at jvm.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:13)
at jvm.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:14)
……
- 定义了大量的本地变量,增加此方法帧中本地变量表的长度。结果:抛出StackoverflowError异常时输出的栈深度相应缩小。
代码:
package jvm;
/**
* -Xss128K
*
* @author Poison
*
*/
public class JavaVMStackSOF {
private int stackLength = 1;
public void stackLeak() {
stackLength++;
stackLeak();
}
public static void main(String[] args) {
JavaVMStackSOF oom = new JavaVMStackSOF();
long e0 = 1;
long e1 = 1;
long e2 = 1;
long e3 = 1;
long e4 = 1;
long e5 = 1;
long e6 = 1;
long e7 = 1;
long e8 = 1;
long e9 = 1;
long q0 = 1;
long q1 = 1;
long q2 = 1;
long q3 = 1;
long q4 = 1;
long q5 = 1;
long q6 = 1;
long q7 = 1;
long q8 = 1;
long q9 = 1;
long r0 = 1;
long r1 = 1;
long r2 = 1;
long r3 = 1;
long r4 = 1;
long r5 = 1;
long r6 = 1;
long r7 = 1;
long r8 = 1;
long r9 = 1;
long t0 = 1;
long t1 = 1;
long t2 = 1;
long t3 = 1;
long t4 = 1;
long t5 = 1;
long t6 = 1;
long t7 = 1;
long t8 = 1;
long t9 = 1;
long y0 = 1;
long y1 = 1;
long y2 = 1;
long y3 = 1;
long y4 = 1;
long y5 = 1;
long y6 = 1;
long y7 = 1;
long y8 = 1;
long y9 = 1;
long u0 = 1;
long u1 = 1;
long u2 = 1;
long u3 = 1;
long u4 = 1;
long u5 = 1;
long u6 = 1;
long u7 = 1;
long u8 = 1;
long u9 = 1;
long i0 = 1;
long i1 = 1;
long i2 = 1;
long i3 = 1;
long i4 = 1;
long i5 = 1;
long i6 = 1;
long i7 = 1;
long i8 = 1;
long i9 = 1;
long o0 = 1;
long o1 = 1;
long o2 = 1;
long o3 = 1;
long o4 = 1;
long o5 = 1;
long o6 = 1;
long o7 = 1;
long o8 = 1;
long o9 = 1;
long p0 = 1;
long p1 = 1;
long p2 = 1;
long p3 = 1;
long p4 = 1;
long p5 = 1;
long p6 = 1;
long p7 = 1;
long p8 = 1;
long p9 = 1;
long a0 = 1;
long a1 = 1;
long a2 = 1;
long a3 = 1;
long a4 = 1;
long a5 = 1;
long a6 = 1;
long a7 = 1;
long a8 = 1;
long a9 = 1;
long s0 = 1;
long s1 = 1;
long s2 = 1;
long s3 = 1;
long s4 = 1;
long s5 = 1;
long s6 = 1;
long s7 = 1;
long s8 = 1;
long s9 = 1;
long d0 = 1;
long d1 = 1;
long d2 = 1;
long d3 = 1;
long d4 = 1;
long d5 = 1;
long d6 = 1;
long d7 = 1;
long d8 = 1;
long d9 = 1;
long f0 = 1;
long f1 = 1;
long f2 = 1;
long f3 = 1;
long f4 = 1;
long f5 = 1;
long f6 = 1;
long f7 = 1;
long f8 = 1;
long f9 = 1;
long g0 = 1;
long g1 = 1;
long g2 = 1;
long g3 = 1;
long g4 = 1;
long g5 = 1;
long g6 = 1;
long g7 = 1;
long g8 = 1;
long g9 = 1;
long h0 = 1;
long h1 = 1;
long h2 = 1;
long h3 = 1;
long h4 = 1;
long h5 = 1;
long h6 = 1;
long h7 = 1;
long h8 = 1;
long h9 = 1;
long j0 = 1;
long j1 = 1;
long j2 = 1;
long j3 = 1;
long j4 = 1;
long j5 = 1;
long j6 = 1;
long j7 = 1;
long j8 = 1;
long j9 = 1;
long k0 = 1;
long k1 = 1;
long k2 = 1;
long k3 = 1;
long k4 = 1;
long k5 = 1;
long k6 = 1;
long k7 = 1;
long k8 = 1;
long k9 = 1;
long l0 = 1;
long l1 = 1;
long l2 = 1;
long l3 = 1;
long l4 = 1;
long l5 = 1;
long l6 = 1;
long l7 = 1;
long l8 = 1;
long l9 = 1;
long z0 = 1;
long z1 = 1;
long z2 = 1;
long z3 = 1;
long z4 = 1;
long z5 = 1;
long z6 = 1;
long z7 = 1;
long z8 = 1;
long z9 = 1;
long c0 = 1;
long c1 = 1;
long c2 = 1;
long c3 = 1;
long c4 = 1;
long c5 = 1;
long c6 = 1;
long c7 = 1;
long c8 = 1;
long c9 = 1;
long v0 = 1;
long v1 = 1;
long v2 = 1;
long v3 = 1;
long v4 = 1;
long v5 = 1;
long v6 = 1;
long v7 = 1;
long v8 = 1;
long v9 = 1;
long b0 = 1;
long b1 = 1;
long b2 = 1;
long b3 = 1;
long b4 = 1;
long b5 = 1;
long b6 = 1;
long b7 = 1;
long b8 = 1;
long b9 = 1;
long n0 = 1;
long n1 = 1;
long n2 = 1;
long n3 = 1;
long n4 = 1;
long n5 = 1;
long n6 = 1;
long n7 = 1;
long n8 = 1;
long n9 = 1;
long m0 = 1;
long m1 = 1;
long m2 = 1;
long m3 = 1;
long m4 = 1;
long m5 = 1;
long m6 = 1;
long m7 = 1;
long m8 = 1;
long m9 = 1;
long qq0 = 1;
long qq1 = 1;
long qq2 = 1;
long qq3 = 1;
long qq4 = 1;
long qq5 = 1;
long qq6 = 1;
long qq7 = 1;
long qq8 = 1;
long qq9 = 1;
long ww0 = 1;
long ww1 = 1;
long ww2 = 1;
long ww3 = 1;
long ww4 = 1;
long ww5 = 1;
long ww6 = 1;
long ww7 = 1;
long ww8 = 1;
long ww9 = 1;
try {
oom.stackLeak();
} catch (Throwable e) {
System.out.println("stack length:" + oom.stackLength);
throw e;
}
}
}
运行结果:
stack length:938
Exception in thread “main” java.lang.StackOverflowError
at jvm.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:13)
at jvm.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:14)
……
实验结果表明:在单个线程下,无论是由于栈帧太大,还是虚拟机栈容量太小,当内存无法分配的时候,虚拟机抛出的都是StackoverflowError异常。
注:Java虚拟机栈是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
如果测试时不限于单线程,通过不断地建立线程的方式倒是可以产生内存溢出异常,但是,这样产生的内存溢出异常与栈空间是否足够大并不存在任何联系,或者准确地说,在这种情况下,给每个线程的栈分配的内存越大,反而越容易产生内存溢出异常。
原因其实不难理解,操作系统分配给每个进程的内存是有限制的,譬如32位的Windows限制为2GB。虚拟机提供了参数来控Java堆和方法区这两部分内存的最大值。剩余的内存为2GB(操作系统限制)减去Xmx(最大堆容量),再减去MaxPermSize(最大方法区容量),程序计数器消耗内存很小,可以忽略掉。如果虚拟机进程本身耗费的内存不计算在内,剩下的内存就由虚拟机栈和本地方法栈“瓜分”了。每个线程分配到的栈容量越大,可以建立的线程数量自然就越少,建立线程时就越容易把剩下的内存耗尽。
例:
package jvm;
/**
* -Xss2M
*
* @author Poison
*
*/
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) {
JavaVMStackOOM oom = new JavaVMStackOOM();
oom.stackLeakByThread();
}
}
注:在Windows平台的虚拟机中,Java的线程是映射到操作系统的内核线程上的,所以上述代码执行时有较大的风险,会使CPU利用率接近100%而导致系统假死。
书上给的运行结果为:
Exception in thread “main” java.lang.OutOfMemoryError: unable to create new native thread
我在测试中未等到出现异常信息机器就近乎卡死了。