Comportamiento extraño de Java OutOfMemoryError

Suponiendo que tenemos una memoria máxima de 256M, ¿por qué funciona este código?

public static void main(String... args) { for (int i = 0; i < 2; i++) { byte[] a1 = new byte[150000000]; } byte[] a2 = new byte[150000000]; } 

pero este arroja un OOME?

 public static void main(String... args) { //for (int i = 0; i < 2; i++) { byte[] a1 = new byte[150000000]; } byte[] a2 = new byte[150000000]; } 

Para mantener las cosas en perspectiva, considere ejecutar este código con -Xmx64m :

 static long sum; public static void main(String[] args) { System.out.println("Warming up..."); for (int i = 0; i < 100_000; i++) test(1); System.out.println("Main call"); test(5_500_000); System.out.println("Sum: " + sum); } static void test(int size) { // for (int i = 0; i < 1; i++) { long[] a2 = new long[size]; sum += a2.length; } long[] a1 = new long[size]; sum += a1.length; } 

Dependiendo de si haces el calentamiento o si lo saltas, se soplará o no. Esto se debe a que el código JITted anula correctamente la var, mientras que el código interpretado no. Ambos comportamientos son aceptables según la Especificación del lenguaje Java, lo que significa que está a merced de la JVM con esto.

Probado con Java HotSpot(TM) 64-Bit Server VM (build 23.3-b01, mixed mode) en OS X.

Análisis de códigos de bytes

Mire el bytecode con el bucle for (código simple, sin la variable de sum ):

 static void test(int); Code: 0: iconst_0 1: istore_1 2: goto 12 5: iload_0 6: newarray long 8: astore_2 9: iinc 1, 1 12: iload_1 13: iconst_1 14: if_icmplt 5 17: iload_0 18: newarray long 20: astore_1 21: return 

y sin:

 static void test(int); Code: 0: iload_0 1: newarray long 3: astore_1 4: iload_0 5: newarray long 7: astore_1 8: return 

No hay null explícita en ninguno de los casos, pero tenga en cuenta que en el no, por ejemplo, la misma ubicación de memoria se reutiliza, en contraste con, por ejemplo. Esto, en todo caso, conduciría a la expectativa opuesta al comportamiento observado.

Un giro...

En función de lo que aprendimos del bytecode, intente ejecutar esto:

 public static void main(String[] args) { { long[] a1 = new long[5_000_000]; } long[] a2 = new long[0]; long[] a3 = new long[5_000_000]; } 

No OOME lanzado . Comente la statement de a2 , y está de vuelta. Asignamos más , pero ocupamos menos ? Mira el bytecode:

 public static void main(java.lang.String[]); Code: 0: ldc #16 // int 5000000 2: istore_1 3: ldc #16 // int 5000000 5: newarray long 7: astore_2 8: iconst_0 9: newarray long 11: astore_2 12: ldc #16 // int 5000000 14: newarray long 16: astore_3 17: return 

La ubicación 2, utilizada para a1 , se reutiliza para a2 . Lo mismo es cierto para el código de OP, pero ahora sobrescribimos la ubicación con una referencia a una matriz inocua de longitud cero y usamos otra ubicación para almacenar la referencia de nuestra enorme matriz.

En resumen...

La Especificación del lenguaje Java no especifica que se deba recostackr ningún objeto basura y la especificación JVM solo dice que el "marco" con variables locales se destruye como un todo al finalizar el método. Por lo tanto, todos los comportamientos que hemos presenciado son por el libro. El estado invisible de un objeto (mencionado en el documento vinculado por keppil ) es solo una forma de describir lo que ocurre en algunas implementaciones y bajo ciertas circunstancias, pero de ningún modo es un tipo de comportamiento canónico.

Esto se debe a que, si bien a1 no está dentro del scope después de los corchetes, se encuentra en un estado llamado invisible hasta que el método retorna.

La mayoría de las JVM modernas no establecen la variable a1 como null tan pronto como sale del ámbito (en realidad, si los paréntesis interiores están allí o no, ni siquiera cambia el código de bytes generado), porque es muy ineficaz y normalmente no lo hace. no importa Por lo tanto, a1 no se puede recolectar basura hasta que regrese el método.

Puedes verificar esto agregando la línea

 a1 = null; 

dentro de los corchetes, lo que hace que el progtwig funcione bien.

El término invisible y la explicación se toman de este documento antiguo: http://192.9.162.55/docs/books/performance/1st_edition/html/JPAppGC.fm.html .