Вывод использования памяти стека метода в 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 не подходит для интенсивного использования рекурсии, как и многие языки. например он не выполняет оптимизацию хвостового вызова, которая в этом случае заставит вашу программу работать вечно.;)