2012-02-24 1 views
15

각 메소드가 실행할 때 소비하는 스택 메모리의 양을 확인하려고합니다. 작업을 수행하려면, 나는 m()가 호출 된 횟수를 말해 정수를 인쇄 단지 StackOverflowError 강제로이 간단한 프로그램,Java에서 메소드의 스택 메모리 사용을 추정합니다.

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(); 
    } 
} 

을 고안했습니다. 나는 수동으로 다음과 같은 값을 획득, 다양한 값 (128K, 256K, 384K)에 JVM의 스택 크기 (-Xss VM 매개 변수)를 설정 한 :

stack i  delta 
    128  1102 
    256  2723 1621 
    384  4367 1644 

델타는 나에 의해 계산하고 마지막 사이의 값입니다했다 라인의 i와 현재의 것. 예상대로 고정됩니다. 그리고 그 문제가 있습니다. 스택 크기 메모리 증가량이 128k 였으므로 호출 당 80byte 메모리 사용과 같은 결과를 낳습니다 (이는 과장된 것처럼 보입니다). 최대 m() BytecodeViewer의에서 찾고

, 우리는 우리는이 정적 방법을 알고 2의 스택의 최대 깊이를 얻을 거기에는 this 매개 변수 전달 없다, 그 m()이 인수가 없다. 또한 리턴 주소 포인터를 고려해야합니다. 그래서 메서드 호출 당 3 * 8 = 24 바이트를 사용해야합니다 (변수 당 8 바이트라고 가정하고 있습니다. 물론 그럴 수 있습니까?). 그것이 그보다 조금더라도, 48 바이트라고 가정 해 봅시다. 우리는 여전히 80 바이트 값에서 멀리 떨어져 있습니다.

메모리 정렬과 관련이있을 수 있다고 생각했지만, 그 경우 약 64 또는 128 바이트의 값을 가질 수 있습니다.

저는 64 비트 Windows7 OS에서 64 비트 JVM을 실행하고 있습니다.

몇 가지 가정을했는데 그 중 일부는 완전히 해제되었을 수 있습니다. 그 경우, 나는 모든 귀입니다.

사람이 당신이 믿지 않는 경우에도 저장되어 다른 컨텍스트 정보를 당신은 스택에 명령 포인터 (8 바이트)를 포함하기 위해 필요한이 I must be frank..

답변

2

이 질문은 내 머리를 아프게 할 수도 있지만 어쩌면 당신은 더 깊은 레벨에서 이것에 대해 이야기하고있을 것입니다. 그러나 어쨌든 내 대답을 던질 것입니다.

먼저, return address pointer은 무엇을 말하고 있습니까? 메서드가 끝나면 스택 프레임에서 반환 메서드가 팝됩니다. 따라서 반환 주소는 실행 메서드 Frame 내에 저장되지 않습니다.

프레임은 로컬 변수를 저장합니다. 정적이고 매개 변수가 없으므로, 여러분이 말한 것처럼 이것들은 비어 있어야하며, op 스택과 locals의 크기는 컴파일 타임에 고정되며, 각각의 단위는 32 비트입니다. 그러나이 메소드는 또한 클래스가 속한 클래스의 상수 풀에 대한 참조를 가져야합니다.

JVM 사양은 컴파일러에 따라 나머지 바이트를 설명 할 수있는 방법 프레임을 may be extended with additional implementation-specific information, such as debugging information.으로 지정합니다.

모든 오픈 JDK 소스 정련 JVM Specification on Frames.

UPDATE

로부터 공급는 메소드 호출에서 프레임에 전달되는 구조체로 나타나는이를 보여준다. 내에서 무엇을 기대해야하는지에 꽤 좋은 통찰력을 제공합니다 :

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

+0

그건 통찰력있는 정보였습니다. 각 메서드 호출이 80 바이트를 차지하는 이유에 대해 이론화 해 주시겠습니까? –

+0

프레임 구조 내에서 내 자신의 JVM 구현이 보유하고있는 정보를 말할 수 있습니까? – Jivings

+0

@devouredelysium OpenJDK 소스로 내 대답을 업데이트했습니다. – Jivings

4

을하고 있어요하고있을 수 있습니다 이유를 물어 시작하기 전에 그것은 있어야 할 것입니다. 정렬은 16 바이트, 힙과 같은 8 바이트가 될 수 있습니다. 예 : 리턴 값이 없어도 8 바이트를 리턴 값으로 예약 할 수 있습니다.

Java는 많은 언어와 같이 재귀를 많이 사용하는 데 적합하지 않습니다. 예 : 이 경우에는 프로그램을 영원히 돌릴 수있는 마무리 호출 최적화를 수행하지 않습니다. ;)

+0

예, 명시 적으로 24bytes가이 개 변수를 더한 반환 주소를 포함한다는 것은 잊어 버렸습니다. –

+3

"다른 컨텍스트 정보가있을 수도 있습니다. 그럴 필요가 있다고 생각지 않아도 저장됩니다."그게 내가 알고 싶은 것입니다! 나는이 문제에 관해 밝혀 줄 누군가에게 쿠키와 술을 제공하고 있습니다! –

+1

JNI 호출에서는 jenv (환경) 및 jclass (클래스)가 포함됩니다. 그것을 해결하는 가장 좋은 방법은 OpenJDK 코드를 읽는 것입니다. –

관련 문제