2015-10-16 2 views
8

Это SO answer разъясняет несколько вещей о флагом JVM -Xmx. Попытка эксперимента я сделал следующее:-Xmx жесткий предел?

import java.util.List; 
import java.util.ArrayList; 

public class FooMain { 

    private static String memoryMsg() { 
     return String.format("%s. %s. %s" 
          , String.format("total memory is: [%d]",Runtime.getRuntime().totalMemory()) 
          , String.format("free memory is: [%d]",Runtime.getRuntime().freeMemory()) 
          , String.format("max memory is: [%d]",Runtime.getRuntime().maxMemory())); 
    } 

    public static void main(String args[]) { 
     String msg = null; 
     try { 
      System.out.println(memoryMsg()); 
      List<Object> xs = new ArrayList<>(); 
      int i = 0 ; 
      while (true) { 
       xs.add(new byte[1000]); 
       msg = String.format("%d 1k arrays added.\n%s.\n" 
            , ++i 
            , memoryMsg()); 
      } 
     } finally { 
      System.out.printf(msg); 
     } 
    } 
} 

Собирать с javac FooMain.java. Когда я запускаю его с максимальным размером кучи 5 миллионов байт я получаю:

java -Xmx5000000 FooMain 
total memory is: [5242880]. free memory is: [4901096]. max memory is: [5767168] 
4878 1k arrays added. 
total memory is: [5767168]. free memory is: [543288]. max memory is: [5767168]. 
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded 
      at java.lang.String.toCharArray(String.java:2748) 
      at java.util.Formatter$FormatSpecifier.print(Formatter.java:3048) 
      at java.util.Formatter$FormatSpecifier.printInteger(Formatter.java:2744) 
      at java.util.Formatter$FormatSpecifier.print(Formatter.java:2702) 
      at java.util.Formatter.format(Formatter.java:2488) 
      at java.util.Formatter.format(Formatter.java:2423) 
      at java.lang.String.format(String.java:2792) 
      at FooMain.memoryMsg(FooMain.java:7) 
      at FooMain.main(FooMain.java:21) 

Хотя эти цифры достаточно близки, они не кажутся очень точно (с исключением total memory в конце достигающего точноmax memory). В частности, 5368 массивов по 1000 байт должны занимать более 5000000 или даже 5242880 байт. Как следует понимать эти цифры?

Это Java я использую:

java version "1.7.0_80" 
Java(TM) SE Runtime Environment (build 1.7.0_80-b15) 
Java HotSpot(TM) Server VM (build 24.80-b11, mixed mode) 
+0

Может быть, сбор мусора заработал за это время ... –

+1

@RobertNiestroj не может, так как все ссылки хранятся –

+1

@RobertNiestroj почему нужно что-нибудь GCed? Я добавляю массивы в «Список». Никакая ссылка не выходит за рамки. –

ответ

5

-Xmx жесткий предел?

Это зависит от того, что вы подразумеваете под «твердым». Если вы имеете в виду «не может быть изменено программой», тогда «Да». Если вы имеете в виду «точный», то №. Размеры различных пространств, которые использует сборщик мусора, кратно некоторой мощности в 2 байта. И, судя по всему, JVM округляется вверх, а не вниз.

Пример программы, которая выделяет 1000 байт массивы

Как следует понимать эти цифры?

1000 байт Java массив не занимает ровно 1000 байт:

  • Существует заголовок объекта, включая пространство для 32-битных length поля. (Обычно 3 x 32 бита).

  • Узлы кучи выделяются кратным 2^N байтам.(Как правило, 2^3 == 8)


Тогда у вас есть вопрос о том, что является причиной OutOfMemoryError. Вы можете подумать, что это потому, что куча полностью заполнена. Однако в этом случае в сообщении говорится, что превышен верхний предел GC. Это означает, что JVM обнаружил, что JVM тратит слишком большой процент от общего времени процессора, используемого сборщиком мусора. Это то, что убило GC ... не исчерпывается память.

Обоснование «предельного предельного значения GC» заключается в том, что когда куча становится полной, GC работает часто и каждый раз восстанавливает меньше и меньше памяти. Когда вы попадаете в «смертельную спираль» GC, лучше быстро вытащить вилку, а не позволять приложению прервать свою конечную точку отказа.

В любом случае ... что это значит, что ваша эвристика для определения того, сколько памяти выделено при заполнении кучи, вероятно, неверно.

6

Глядя на исходный код OpenJDK, логика, чтобы определить максимальный размер кучи является довольно сложным, и определяется множеством переменных. Фактический размер кучи устанавливаются в hotspot/src/share/vm/memory/collectorPolicy.cpp, он использует предоставленное значение -Xmx в качестве входных данных, и выравнивает его, используя следующий код:

align_size_up(MaxHeapSize, max_alignment()); 

align_size_up определяются как:

#define align_size_up_(size, alignment) (((size) + ((alignment) - 1)) & ~((alignment) - 1)) 

И max_alignment является продуктом размера страницы виртуальной памяти и размера карты сбора мусора JVM. Размер страницы VM составляет 4096 байт, а размер карты - 512 байт. Включение этих значений дает фактический MaxHeapSize из 6324224 байта, который хорошо соответствовал бы номерам, которые вы видите.

Примечание: я только кратко посмотрел код JVM, так что возможно, что я что-то пропустил, но ответ, похоже, складывается с тем, что вы видите.

1

Ваш вопрос в значительной степени ответил на других людей, в настоящее время, но из интереса я сделал математику :-)

Он заявил here:

Это значение должно кратно 1024 больше 2 МБ.

5000000 не многократный 1024. Я предполагал, что аргумент округляется до кратного 1024, что подтверждают другие ответы. Ваша общая память (которая больше, чем просто объявленное пространство кучи) составляет 5767168, что составляет 5632 * 1024. Он был увеличен во время выполнения от начального 5242880, чтобы соответствовать растущей площади кучи. Обратите внимание, что -Xmx объявляет максимум, поэтому он не обязательно сразу выделяется. С помощью this source мы можем приблизительное использование памяти (при условии, 64bit):

  • 4878000 байты для байта
  • 4878 * 24 = 117072 байт массива накладных
  • несколько байт для других создаваемых объектов и строк
  • небольшое колебание памяти из-за сбора мусора (по крайней мере, строка, которую вы создаете для каждой итерации, может быть выброшена)

S o массивы занимают 4995072 байта, которые (по совпадению?) уже были бы кратными 1024. Но все еще накладные расходы для других объектов. Таким образом, пространство кучи несколько кратно 1024 больше 4995072 и ниже 5767168. Учитывая свободное пространство в конце, это оставляет нас с 228808 байтами для остальной части памяти кучи и памяти без кучи, которая звучит как правдоподобное число.

Наконец-то слово на свободном пространстве осталось в конце. JVM не обязательно заполняет всю память перед сбоем. Если память близка к завершению, сбор мусора работает чаще. Если это занимает определенный процент от общего времени выполнения, the JVM exits, что произошло в вашем случае.

Смежные вопросы