在Java中推断方法的堆栈内存使用

我正在尝试确定每个方法在运行时消耗的堆栈内存量。 为了完成这项任务,我设计了这个简单的程序,只强制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。

我做了几个假设,其中一些可能完全没有。 就是这样,我全都听见了。

在任何人开始问我为什么要这样做之前我必须坦率地说..

这个问题可能在我的脑海中浮现,也许你在更深层次上讨论这个问题,但无论如何我都会把答案扔出去。

首先,你通过return address pointer引用什么? 方法完成后,将从堆栈框架中弹出返回方法。 因此,执行方法Frame中不存储返回地址。

方法Frame存储局部变量。 因为它是静态的,无参数的,所以这些应该是空的,并且op堆栈和locals的大小在编译时是固定的,每个单元的宽度为32位。 但是,除此之外,该方法还必须引用它所属的类的常量池。

此外,JVM规范指定may be extended with additional implementation-specific information, such as debugging information.方法帧may be extended with additional implementation-specific information, such as debugging information. 这可以解释剩余的字节,具体取决于编译器。

所有这些都来自JVM规范框架。

UPDATE

搜索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; 

资源

您需要在堆栈中包含指令指针(8个字节),并且可能存在其他上下文信息,即使您不相信它也需要保存。 对齐可以是16个字节,像堆一样是8个字节。 例如,即使没有返回值,也可以为返回值保留8个字节。

Java不像许多语言那样适合大量使用递归。 例如,它不进行尾调用优化,在这种情况下会导致程序永远运行。 ;)