2012-02-24 7 views
15

Sto provando a determinare la quantità di memoria di stack che ogni metodo consuma durante l'esecuzione. Per fare il compito, ho ideato questo semplice programma che sarà solo forzare un StackOverflowError,Inversione dell'uso della memoria di stack di un metodo in 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(); 
    } 
} 

stampa di un numero intero a dirmi quante volte m() è stato chiamato. Ho impostato manualmente dimensione dello stack del JVM (-Xss parametro VM) a valori variabili (128k, 256k, 384k), ottenendo i seguenti valori:

stack i  delta 
    128  1102 
    256  2723 1621 
    384  4367 1644 

delta è stato calcolato da me, ed è il valore tra l'ultimo line's i e quello corrente. Come previsto, è stato risolto. E qui sta il problema. Come so, l'incremento della memoria della dimensione dello stack era di 128k, che produce qualcosa come un 80byte di memoria per chiamata (che sembra esagerato).

Alzando m() in BytecodeViewer di, otteniamo la profondità massima di una pila di 2. Sappiamo che questo è un metodo statico e che non c'è this passaggio di parametri, e che m() non ha argomenti. Dobbiamo anche prendere in considerazione il puntatore dell'indirizzo di ritorno. Quindi ci dovrebbe essere qualcosa come 3 * 8 = 24 byte usati per la chiamata al metodo (sto assumendo 8 byte per variabile, che ovviamente potrebbero essere completamente off. E '?). Anche se è un po 'più di questo, diciamo 48 byte, siamo ancora lontani dal valore di 80bytes.

Ho pensato che potesse avere qualcosa a che fare con l'allineamento della memoria, ma la verità è che in tal caso avremmo un valore di circa 64 o 128 byte, direi.

Sono in esecuzione una JVM a 64 bit con un sistema operativo Windows a 64 bit.

Ho fatto diverse ipotesi, alcune delle quali potrebbero essere totalmente off. Essendo questo il caso, sono tutto orecchie.

Prima che qualcuno comincia a fare questo che sto facendo questo I must be frank..

risposta

2

Questa domanda potrebbe essere al di sopra della mia testa, forse stai parlando di questo a un livello più profondo, ma ti lascio comunque la risposta.

In primo luogo, a cosa si riferisce return address pointer? Quando un metodo è finito, il metodo di ritorno viene estratto dal frame dello stack. Quindi nessun indirizzo di ritorno viene memorizzato all'interno del metodo di esecuzione Frame.

Il metodo Frame memorizza le variabili locali. Dal momento che è statico e senza parametri, questi dovrebbero essere vuoti come dici tu, e le dimensioni della pila op e dei locali sono fisse in fase di compilazione, con ciascuna unità in ciascuna di 32 bit di larghezza. Ma oltre a questo il metodo deve anche avere un riferimento al pool costante della classe a cui appartiene.

In aggiunta, la specifica JVM specifica i telegrammi del metodo may be extended with additional implementation-specific information, such as debugging information. Che potrebbe spiegare i restanti byte, a seconda del compilatore.

Tutti provenienti da JVM Specification on Frames.

UPDATE

Cerchi una sorgente OpenJDK rivela questo, che sembra essere la struct che viene passato a fotogrammi metodo. Dà una buona comprensione su cosa aspettarsi entro:

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

Questa era un'informazione interessante, signore. Potresti teorizzare sul perché ogni chiamata al metodo sembra richiedere 80 byte? –

+0

Posso dirti quali informazioni contiene la mia implementazione JVM all'interno della struttura Frame? – Jivings

+0

@devouredelysium Aggiornato la mia risposta con l'origine OpenJDK. – Jivings

4

È necessario includere nello stack il puntatore all'istruzione (8 byte) e non ci possono essere altre informazioni di contesto che viene salvato anche se non credete dovrebbe essere. L'allineamento potrebbe essere di 16 byte, 8 byte come lo è l'heap. per esempio. potrebbe riservare 8 byte per il valore di ritorno anche se non ce n'è uno.

Java non è adatto all'uso intensivo della ricorsione come lo sono molte lingue. per esempio. non fa un'ottimizzazione di coda che in questo caso farebbe funzionare il tuo programma per sempre. ;)

+0

Sì, ho dimenticato di indicare esplicitamente che i 24bytes inclusi i 2 variabili, più l'indirizzo di ritorno. –

+3

"potrebbero esserci altre informazioni di contesto che vengono salvate anche se non credete che sarebbe necessario." Questo è quello che voglio sapere! Sto dando biscotti e alcol a chiunque sia disponibile a far luce sul problema! –

+1

Nelle chiamate JNI, sono inclusi jenv (environment) e jclass (la classe). Il modo migliore per risolverlo è leggere il codice OpenJDK. –

Problemi correlati