Java OutOfMemoryError странное поведение


предполагая, что у нас есть максимальная память 256 м, почему этот код работает:

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

но это один бросить ум?

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

2 ответа:

чтобы держать вещи в перспективе, используйте этот код -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;
}

в зависимости от того, делаете ли вы разминку или пропустить его, он будет дуть или не дуть. Это потому, что JITted код правильно nullS из var, тогда как интерпретируемый код не делает. оба поведения приемлемы в соответствии со спецификацией языка Java, что означает, что вы находитесь во власти JVM с этим.

Проверено с Java HotSpot(TM) 64-Bit Server VM (build 23.3-b01, mixed mode) на ОС Х.

анализ байт-кода

посмотрите на байт-код с for цикл (простой код, без 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

и без:

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

нет явного nulling выходит в любом случае, но обратите внимание, что в нет пример одна и та же ячейка памяти фактически используется повторно, в отличие от на пример. Это, во всяком случае, приведет к ожиданию, противоположному наблюдаемому поведение.

поворот...

основываясь на том, что мы узнали из байт-кода, попробуйте запустить это:

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

никто не бросил. Прокомментируйте объявление a2, а он вернулся. Мы выделить больше, а занимают меньше? Посмотрите на байт-код:

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        

расположение 2, используется для a1, используется для a2. То же самое верно для кода OP, но теперь мы перезаписываем местоположение с помощью ссылка на безобидный массив нулевой длины и использование другого места для хранения ссылки на наш огромный массив.

подводя итог...

спецификация языка Java не указывает, что какой-либо объект мусора должны быть собранным, и спецификация JVM только говорит, что "кадр" с локальными переменными уничтожается в целом по завершении метода. Поэтому все поведение, которое мы наблюдали, соответствует книге. Элемент невидимка состояние объекта (упоминается в документе, связанном с keppil) - это просто способ описать то, что происходит в некоторых реализациях и при некоторых обстоятельствах, но никоим образом не является каноническим поведением.

это потому что пока a1 не находится в области после скобок, он находится в состоянии под названием невидимка пока метод возвращает.

большинство современных JVMs не устанавливают переменную a1 to null как только он покидает область (на самом деле, есть ли внутренние скобки или нет, даже не меняет сгенерированный байтовый код), потому что он очень неэффективен и обычно не имеет значения. Таким образом, a1 невозможно собрать мусор, пока метод не вернется.

вы можете проверить это, добавив строку

a1 = null;

внутри скобок, что делает программу работать нормально.

термин невидимка и объяснение взято из старой бумаги: http://192.9.162.55/docs/books/performance/1st_edition/html/JPAppGC.fm.html.