我正在尝试确定每个方法在运行时消耗的堆栈内存量。为了完成这项任务,我设计了这个简单的程序,只强制StackOverflowError
,
public class Main {
private static int i = 0;
public static void main(String[] args) {
try {
m();
} catch (StackOverflowError e) {
System.err.println(i);
}
}
private static void m() {
++i;
m();
}
}
打印一个整数,告诉我调用m()
的次数。我手动将JVM的堆栈大小(-Xss
VM参数)设置为不同的值(128k,256k,384k),获得以下值:
stack i delta
128 1102
256 2723 1621
384 4367 1644
delta由我计算,它是最后一行i和当前行之间的值。正如所料,它是固定的。这就是问题所在。据我所知,堆栈大小的内存增量为128k,这样每次调用就会产生80byte的内存(这看起来有点夸张)。
在BytecodeViewer中查找m()
,我们得到一个堆栈的最大深度为2.我们知道这是一个静态方法,并且没有传递this
个参数,并且m()
没有参数。我们还必须考虑返回地址指针。因此每个方法调用应该使用3 * 8 = 24个字节(我假设每个变量有8个字节,当然可能完全关闭。是吗?)。即使它比这更多,让我们说48字节,我们仍然远离80字节的值。
我认为它可能与内存对齐有关,但事实是,在这种情况下,我们会有大约64或128字节的值,我会说。
我在64位Windows7操作系统下运行64位JVM。
我做了几个假设,其中一些可能完全没有。就是这样,我全都听见了。
在有人开始问我为什么这样做I must be frank..
之前答案 0 :(得分:4)
您需要在堆栈中包含指令指针(8个字节),并且可能存在其他上下文信息,即使您不相信它也需要保存。对齐可以是16个字节,像堆一样是8个字节。例如它可以为返回值保留8个字节,即使没有。
Java不像许多语言那样适合大量使用递归。例如它不会进行尾调用优化,在这种情况下会导致程序永远运行。 ;)
答案 1 :(得分:2)
这个问题可能在我的脑海中浮出水面,也许你在更深层次上讨论这个问题,但无论如何我都会把答案抛在那里。
首先,return address pointer
您提到了什么?方法完成后,将从堆栈框架中弹出返回方法。因此,执行方法Frame中不存储返回地址。
方法Frame存储局部变量。因为它是静态的,无参数的,所以这些应该是空的,并且op堆栈和locals的大小在编译时是固定的,每个单元的宽度为32位。但是,除此之外,该方法还必须引用它所属的类的常量池。
另外,JVM规范指定方法帧may be extended with additional implementation-specific information, such as debugging information.
这可以解释剩余的字节,具体取决于编译器。
全部来自JVM Specification on Frames.
<强>更新强>
搜索OpenJDK源会显示这一点,它似乎是在方法调用时传递给Frames的结构。对内容的期望提供了很好的洞察力:
/* Invoke types */
#define INVOKE_CONSTRUCTOR 1
#define INVOKE_STATIC 2
#define INVOKE_INSTANCE 3
typedef struct InvokeRequest {
jboolean pending; /* Is an invoke requested? */
jboolean started; /* Is an invoke happening? */
jboolean available; /* Is the thread in an invokable state? */
jboolean detached; /* Has the requesting debugger detached? */
jint id;
/* Input */
jbyte invokeType;
jbyte options;
jclass clazz;
jmethodID method;
jobject instance; /* for INVOKE_INSTANCE only */
jvalue *arguments;
jint argumentCount;
char *methodSignature;
/* Output */
jvalue returnValue; /* if no exception, for all but INVOKE_CONSTRUCTOR */
jobject exception; /* NULL if no exception was thrown */
} InvokeRequest;