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

dev*_*ium 15 java stack profiling jvm

我正在尝试确定每个方法在运行时消耗的堆栈内存量.为了完成这项任务,我设计了这个简单的程序,它只会强制执行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();
    }
}
Run Code Online (Sandbox Code Playgroud)

打印一个整数,告诉我m()调用了多少次.我手动将JVM的堆栈大小(-XssVM参数)设置为不同的值(128k,256k,384k),获得以下值:

   stack    i       delta
    128     1102
    256     2723    1621
    384     4367    1644
Run Code Online (Sandbox Code Playgroud)

delta由我计算,它是最后一行i和当前行之间的值.正如所料,它是固定的.这就是问题所在.据我所知,堆栈大小内存增量为128k,这样每次调用就会产生80byte内存(这看起来有点夸张).

查看m()BytecodeViewer,我们得到堆栈的最大深度为2.我们知道这是一个静态方法,并且没有this参数传递,并且m()没有参数.我们还必须考虑返回地址指针.因此每个方法调用应该使用3*8 = 24个字节(我假设每个变量有8个字节,当然可能完全关闭.是吗?).即使它比这更多,让我们说48字节,我们仍然远离80字节的价值.

我认为它可能与内存对齐有关,但事实是,在这种情况下,我们会有大约64或128字节的值,我会说.

我在64位Windows7操作系统下运行64位JVM.

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

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

Jiv*_*ngs 2

这个问题可能超出了我的理解范围,也许你正在更深层次地谈论这个问题,但无论如何我都会给出我的答案。

首先,你指的是什么return address pointer?当一个方法完成时,返回方法将从堆栈帧中弹出。因此,执行方法 Frame 中不存储返回地址。

Frame 方法存储局部变量。由于它是静态的且无参数,所以正如您所说,这些应该是空的,并且操作堆栈和局部变量的大小在编译时是固定的,每个单元都是 32 位宽。但除此之外,该方法还必须具有对其所属类的常量池的引用。

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

全部源自JVM 框架规范。

更新

搜索 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;
Run Code Online (Sandbox Code Playgroud)

来源