55 votes

Java OutOfMemoryError comportement étrange

En supposant que nous avons un max de mémoire de 256 MO, pourquoi ce code fonctionne:

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

mais celui-ci lève une OOME?

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

35voto

Marko Topolnik Points 77257

Pour garder les choses en perspective, envisager d'exécuter ce code avec -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;
}

Selon si vous ne l'échauffement ou l'ignorer, elle va se faire sauter ou ne pas sauter. C'est parce que le JITted code correctement nulls le var, alors que le code n'est pas interprété. Les deux comportements sont acceptables en vertu de la Java Langage de Spécification, ce qui signifie que vous êtes à la merci de la JVM avec cette.

Testé avec Java HotSpot(TM) 64-Bit Server VM (build 23.3-b01, mixed mode) sur OS X.

Le Bytecode de l'analyse

Regarder le bytecode avec l' for boucle (code simple, sans l' sum variable):

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

et sans:

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

Pas explicite nulling dans les deux cas, mais notez que dans le pas-par exemple le même emplacement de mémoire est effectivement réutilisé, en contraste avec la par exemple. Ce serait, si tout, conduire à l'attente face au comportement observé.

Un twist...

Basé sur ce que nous avons appris à partir du bytecode, l'essayer:

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];
}

Pas de OOME jeté. Commentaire de la déclaration de l' a2, et il est de retour. Nous allouer plus, mais occupent moins? Regardez le pseudo-code:

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        

L'emplacement 2, utilisé pour a1, est réutilisé pour l' a2. Le même est vrai pour des OP de code, mais maintenant nous écraser l'emplacement avec une référence à une inoffensive zéro-longueur de la matrice, et d'utiliser un autre emplacement pour stocker la référence à notre grand tableau.

Pour résumer...

Le champ d'application de l'accessibilité d'un objet affecté à une variable locale n'est pas spécifié par le Langage Java Spécification et la JVM spec seulement dit que le "cadre" avec des variables locales est détruit dans son ensemble sur la méthode de l'achèvement. Par conséquent, tous les comportements que nous avons observés sont par le livre. L' invisible, de l'état d'un objet (mentionné dans le document lié par keppil) est juste une façon de décrire ce qui se passe dans certaines implémentations et dans certaines circonstances, mais n'est en aucune façon tout type de mode canonique.

26voto

Keppil Points 28356

C'est parce que tout a1 n'est pas à la portée après les crochets, il est dans un état appelé invisible jusqu'à ce que le retour de la méthode.

Plus modernes, les machines virtuelles ne pas définir la variable a1 de null dès qu'il quitte le champ (en fait, si les parenthèses sont là ou pas n'est même pas changer le générés byte code), car il est très inefficace, et n'a généralement pas d'importance. Par conséquent, a1 ne peut pas être nettoyée jusqu'à ce que le retour de la méthode.

Vous pouvez le vérifier en ajoutant la ligne

a1 = null;

entre parenthèses, ce qui rend le programme fonctionne parfaitement.

Le terme invisible et l'explication en est pris à partir de ce vieux papier: http://192.9.162.55/docs/books/performance/1st_edition/html/JPAppGC.fm.html.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X