exceeded - java.lang.outofmemoryerror: java heap space linux
Comportamiento extraño de Java OutOfMemoryError (2)
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];
}
Esto se debe a que, si bien a1
no está dentro del alcance 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 programa 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
.
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 declaración 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 recopilar 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.