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

2022-01-22 00:00:00 profiling jvm stack java

I'm trying to determine how much stack memory each method consumes when running. To do the task, I've devised this simple program that will just force a 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();
    }
}

printing an integer telling me how many times m() was called. I've manually set the JVM's stack size(-Xss VM parameter) to varying values (128k, 256k, 384k), obtaining the following values:

   stack    i       delta
    128     1102
    256     2723    1621
    384     4367    1644

delta was calculated by me, and it's the value between the last line's i and the current one's. As expected it is fixed. And there lies the problem. As I know the stack size memory increment was by 128k, that yields something like a 80byte memory use per call (which seems exaggerated).

Looking up m() in BytecodeViewer's, we get a stack's max depth of 2. We know this is a static method and that there's no this parameter passing, and that m() has no arguments. We must also take into consideration the return address pointer. So there should be something like 3 * 8 = 24 bytes used per method call (I'm assuming 8 bytes per variable, which of course may be totally off. Is it?). Even if it's a bit more than that, let's say 48bytes, we're still far away from the 80bytes value.

I thought it could have something to do with memory alignment, but truth is that in that case we'd have a value of roughly 64 or 128 bytes, I'd say.

I'm running a 64bit JVM under a 64bit Windows7 OS.

I've made several assumptions, some of which may be totally off. Being that the case, I'm all ears.

Before anyone starts asking why I'm doing this I must be frank..

解决方案

This question may be way over my head, perhaps you are talking about this on a deeper level, but I'll throw my answer out there anyway.

Firstly, what do you refer to by return address pointer? When a method is finished the return method is popped from the stack frame. So no return address is stored within the executing method Frame.

The method Frame stores local variables. Since it's static, and parameterless, these should be empty as you say, and the sizes of the op stack and locals are fixed at compile time, with each unit in each being 32bits wide. But as well as this the method also must have a reference to the constant pool of the class to which it belongs.

In additional the JVM spec specifies that method frames may be extended with additional implementation-specific information, such as debugging information. Which could explain the remaining bytes, depending on the compiler.

All sourced from the JVM Specification on Frames.

UPDATE

Scouring the OpenJDK source reveals this, which appears to be the struct that is passed to Frames on method invocation. Gives a pretty good insight on what to expect within:

/* 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

相关文章