Вывод использования памяти стека метода в 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 была рассчитана мной, и это значение между последней строкой я и текущей. Как и ожидалось, он исправлен. И есть проблема. Поскольку я знаю, что размер памяти размера стека был на 128 тыс., Что дает примерно 80 байтов использования памяти за вызов (что кажется преувеличенным).

Поднимая m() в BytecodeViewer, мы получаем максимальную глубину стека 2. Мы знаем, что это статический метод и что нет параметра this, и что m() не имеет аргументов. Мы также должны учитывать указатель обратного адреса. Таким образом, должно быть что-то вроде 3 * 8 = 24 байта, используемого для вызова метода (я принимаю 8 байтов на переменную, что, конечно, может быть полностью отключено.)? Даже если это немного больше, скажем 48 байтов, мы все еще далеки от значения 80 байтов.

Я думал, что это может быть связано с выравниванием памяти, но правда в том, что в этом случае мы бы имели значение примерно 64 или 128 байт, я бы сказал.

Я запускаю 64-битную JVM под 64-разрядной ОС Windows7.

Я сделал несколько предположений, некоторые из которых могут быть полностью отключены. В этом случае я все уши.

Прежде чем кто-нибудь начнет спрашивать, почему я делаю это Я должен быть откровенным..

Ответы

Ответ 1

Этот вопрос может быть у меня над головой, возможно, вы говорите об этом на более глубоком уровне, но я все равно отправлю свой ответ.

Во-первых, на что вы ссылаетесь return address pointer? Когда метод завершен, метод возврата извлекается из фрейма стека. Поэтому адрес возврата не сохраняется в кадре выполнения.

Метод Frame хранит локальные переменные. Поскольку они статичны и без параметров, они должны быть пустыми, как вы говорите, а размеры стека op и locals фиксируются во время компиляции, причем каждый блок в каждом из них имеет ширину 32 бита. Но также и этот метод также должен иметь ссылку на константный пул класса, к которому он принадлежит.

В дополнении спецификация JVM указывает, что кадры метода 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;

Источник

Ответ 2

Вам нужно включить в стек указатель инструкции (8 байтов), и может быть другая информация контекста, которая сохраняется, даже если вы не считаете, что это должно быть. Выравнивание может быть 16 байт, 8 байтов, как куча. например он может зарезервировать 8 байтов для возвращаемого значения, даже если его нет.

Java не подходит для интенсивного использования рекурсии, как и многие языки. например он не выполняет оптимизацию хвостового вызова, которая в этом случае заставит вашу программу работать вечно.;)