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

时间:2012-02-24 14:21:30

标签: java jvm profiling stack

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

之前

2 个答案:

答案 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;

Source