2012-02-24 27 views
15

我想确定每个方法在运行时消耗多少堆栈内存。做任务,我已经设计了这个简单的程序,只会迫使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(); 
    } 
} 

打印整数告诉我m()了多少次调用。我手动设置JVM的堆栈大小(-Xss VM参数)不同的值(128K,256K,384K),获得以下值:

stack i  delta 
    128  1102 
    256  2723 1621 
    384  4367 1644 

三角洲是由我计算,它的最后的值线我和现在的。如预期的那样,它是固定的这就是问题所在。据我所知,栈大小的内存增量是128k,这可以产生类似于每个调用80byte的内存使用情况(这看起来很夸张)。

在BytecodeViewer中查找m(),我们得到一个堆栈的最大深度为2.我们知道这是一个静态方法,并且没有this参数传递,并且m()没有参数。我们还必须考虑返回地址指针。所以应该有类似于每个方法调用的3 * 8 = 24个字节(我假设每个变量8个字节,这当然可能完全关闭,是吗?)。即使比这还要多一点,比如说48bytes,我们仍然远离80bytes的值。

我认为这可能与内存对齐有关,但事实是,在这种情况下,我们会有大约64或128字节的值,我会说。

我在64位Windows7操作系统下运行64位JVM。

我做了几个假设,其中一些可能完全关闭。既然如此,我就是耳朵。

在任何人开始问我为什么做这个I must be frank..

回答

2

这个问题可能会超出我的头,也许你正在更深层次地讨论这个问题,但我仍然会把我的答案抛出去。

首先,你指的是return address pointer?当一个方法完成时,返回方法从堆栈框架弹出。所以没有返回地址存储在正在执行的方法框架内。

方法Frame存储局部变量。由于它是静态的和无参数的,因此它们应该是空的,并且在编译时固定操作堆栈和本地的大小,每个单元的宽度都是32位。但是,除此之外,该方法还必须提及其所属类别的常量池。

另外,JVM规范指定方法框架may be extended with additional implementation-specific information, such as debugging information.这可以解释剩余字节,具体取决于编译器。

JVM Specification on Frames.

UPDATE

精练OpenJDK的源的所有来源的揭示本,这似乎是被传递到上方法调用的帧的结构。给出了什么期望中一个很好的启示:

/* 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个字节? – 2012-02-26 00:53:31

+0

我可以告诉你我自己的JVM实现在Frame结构中保存了什么信息? – Jivings 2012-02-26 01:19:04

+0

@devouredelysium用OpenJDK来源更新了我的答案。 – Jivings 2012-02-26 01:35:43

4

您需要在堆栈包括指令指针(8个字节),并有可能即使你不相信这是保存其它上下文信息它需要是的。对齐可以是16个字节,像堆一样是8个字节。例如即使没有一个,它也可以为返回值保留8个字节。

Java不适合像很多语言那样大量使用递归。例如它不会执行尾部优化,在这种情况下会导致程序永远运行。 ;)

+0

是的,我忘了明确规定了24bytes包括2个变量加上返回地址。 – 2012-02-24 14:44:41

+3

“即使您不相信它也需要保存,也可能会保存其他上下文信息。”这就是我想知道的!我正在给任何人提供饼干和酒,以解决问题中的一些亮点! – 2012-02-24 14:46:02

+1

在JNI调用中,包含了jenv(环境)和jclass(类)。解决这个问题的最好方法是阅读OpenJDK代码。 – 2012-02-24 14:55:49