您的位置:首页 >虚拟机栈溢出实战解析
发布于2025-07-29 阅读(0)
扫一扫,手机访问
在HotSpot虚拟机中,由于不区分虚拟机栈和本地方法栈,因此-Xoss参数(用于设置本地方法栈大小)实际上是无效的,栈容量仅由-Xss参数设定。
Java虚拟机规范中提到了两种与虚拟机栈和本地方法栈相关的异常:
如果线程请求的栈深度超过虚拟机允许的最大深度,将会抛出StackOverflowError异常。如果虚拟机在扩展栈时无法申请到足够的内存空间,则会抛出OutOfMemoryError异常。在单线程情况下,尝试了以下两种方法,但都无法引发OutOfMemoryError异常,结果都是抛出了StackOverflowError异常:
/** * -Xss128k */
public class JavaVmStackSOF {
private int stackLength = 1;
public void stackLeak() {
stackLength++;
stackLeak();
}
public static void main(String[] args) {
JavaVmStackSOF vm = new JavaVmStackSOF();
try {
vm.stackLeak();
} catch (Throwable e) {
System.out.println("stackLength:" + vm.stackLength);
throw e;
}
}
}运行结果:

由此可以看出,在单线程下,无论是由于栈帧过大还是虚拟机栈容量过小,当内存无法分配时,虚拟机抛出的都是StackOverflowError异常。
在多线程情况下,如果测试不限于单线程,通过不断建立线程的方式确实可以引发内存溢出异常。但这种情况下产生的内存溢出异常与栈空间大小没有直接关系,或者更准确地说,为每个线程分配的栈内存越大,反而越容易引发内存溢出异常。
代码如下:注意:在Windows平台的虚拟机中,Java线程映射到操作系统的内核线程,运行以下代码有较大风险,可能导致操作系统卡死。请谨慎运行!(我已经试验过,电脑会卡死T_T)
/** * -Xss2M */
public class JavaStackOOM {
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) {
JavaStackOOM oom = new JavaStackOOM();
oom.stackLeakByThread();
}
}结果:

原因不难理解,操作系统为每个进程分配的内存是有限的,例如32位Windows限制为2GB。虚拟机提供了参数来控制Java堆和方法区的最大值。剩余的内存为2GB(操作系统限制)减去Xmx(最大堆容量),再减去MaxPermSize(最大方法区容量),程序计数器消耗的内存可以忽略不计。如果不计算虚拟机进程本身消耗的内存,剩下的内存就由虚拟机栈和本地方法栈共享。每个线程分配到的栈容量越大,可以建立的线程数量自然就越少,建立线程时就越容易耗尽剩余内存。
在开发多线程应用时要特别注意,出现StackOverflowError异常时有错误堆栈可以阅读,相对来说,比较容易找到问题所在。而且,如果使用虚拟机默认参数,栈深度在大多数情况下(因为每个方法压入栈的帧大小不同,所以只能说在大多数情况下)达到1000~2000完全没有问题,对于正常的方法调用(包括递归),这个深度应该完全足够。但是,如果是由于建立过多线程导致的内存溢出,在不能减少线程数或更换64位虚拟机的情况下,只能通过减少最大堆和减少栈容量来换取更多的线程。如果没有这方面的处理经验,这种通过“减少内存”的手段来解决内存溢出的方式会比较难以想到。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
4
5
6
7
8
9