Вывод использования стека памяти метода в Java - PullRequest
15 голосов
/ 24 февраля 2012

Я пытаюсь определить, сколько стековой памяти использует каждый метод при запуске. Чтобы выполнить задачу, я разработал эту простую программу, которая просто вызовет 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

дельта была рассчитана мной, и это значение между последней строкой i и текущей. Как и ожидалось, это исправлено. И здесь кроется проблема. Как я знаю, приращение памяти в размере стека составило 128 КБ, что дает примерно 80-байтовое использование памяти на вызов (что кажется преувеличенным).

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

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

Я использую 64-битную JVM под 64-битной ОС Windows7.

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

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

Ответы [ 2 ]

4 голосов
/ 24 февраля 2012

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

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

2 голосов
/ 26 февраля 2012

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

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

Метод Frame хранит локальные переменные. Поскольку он статичен и не имеет параметров, они должны быть пустыми, как вы говорите, а размеры стека операций и локальных файлов фиксированы во время компиляции, причем каждая единица в каждом имеет ширину 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;

Источник

...