Почему -Xmx и Runtime.maxMemory не согласны

Когда вы добавляете

 -Xmx????m

в командной строке, JVM дает вам кучу, которая близка к этому значению, но может быть отключена до 14%. JVM может дать вам гораздо более близкую к тому, что вы хотите, но только путем проб и ошибок.

 System.out.println(Runtime.getRuntime().maxMemory());

печатает

-Xmx1000m ->  932184064
-Xmx1024m -Xmx1g ->  954728448
-Xmx1072m ->  999292928
-Xmx1073m -> 1001390080

Я запускаю обновление HotSpot Java 8 5.

Очевидно, что куча может быть чем-то выше 1000000000, но почему это -Xmx1073m вместо -Xmx1000m?

BTW 1g == 1024m, который предполагает, что 1g должен быть 1024 ^ 3, что на 7% выше 1000 ^ 3, но вы получаете что-то на 7% ниже 1000 ^ 3.


Из-за чего так много говорит о том, что мне не хватает чего-то фундаментального в том, как работает куча. Если бы я попросил -Xmx1000m, и это было 1001390080, мне было бы все равно, я бы предположил, что есть некоторое распределение, которое нужно придерживаться, но чтобы дать вам 932184064, мне кажется, что куча сложнее, чем я могу представьте себе.


EDIT Я обнаружил, что

-Xmx1152m gives 1073741824 which is exactly 1024^3

так что кажется, что он дает мне ровно на 128 МБ меньше, чем я просил в этом случае cf maxMemory().


BTW 128 - мой любимый номер. Сегодня я был на конференции по номеру улицы 128, и оратор цитировал книгу со страницы 128;)

Ответ 1

Разница, по-видимому, объясняется размером пространства для выживания сборщика мусора.

Флаг -Xmx, как описано в docs, управляет максимальным размером пула распределения памяти. Куча части пула распределения памяти разделена на пространства Eden, Survivor и Tenured. Как описано в этом ответе, есть два региона, оставшихся в живых, только один из которых доступен для хранения живых объектов в любой момент времени. Таким образом, общее видимое пространство, доступное для выделения объектов, как сообщается Runtime.maxMemory(), должно вычитать размер одного из оставшихся в живых мест из пула памяти кучи.

Вы можете использовать классы MemoryMXBean и MemoryPoolMXBean, чтобы получить немного больше информации о распределении памяти. Вот простая программа, которую я написал:

import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryPoolMXBean;

public class MemTest {
  static String mb (long s) {
    return String.format("%d (%.2f M)", s, (double)s / (1024 * 1024));
  }

  public static void main(String[] args) {
    System.out.println("Runtime max: " + mb(Runtime.getRuntime().maxMemory()));
    MemoryMXBean m = ManagementFactory.getMemoryMXBean();

    System.out.println("Non-heap: " + mb(m.getNonHeapMemoryUsage().getMax()));
    System.out.println("Heap: " + mb(m.getHeapMemoryUsage().getMax()));

    for (MemoryPoolMXBean mp : ManagementFactory.getMemoryPoolMXBeans()) {
      System.out.println("Pool: " + mp.getName() + 
                         " (type " + mp.getType() + ")" +
                         " = " + mb(mp.getUsage().getMax()));
    }
  }
}

Выход этого в OpenJDK 7 для java -Xmx1024m MemTest:

Runtime max: 1037959168 (989.88 M)
Non-heap: 224395264 (214.00 M)
Heap: 1037959168 (989.88 M)
Pool: Code Cache (type Non-heap memory) = 50331648 (48.00 M)
Pool: Eden Space (type Heap memory) = 286326784 (273.06 M)
Pool: Survivor Space (type Heap memory) = 35782656 (34.13 M)
Pool: Tenured Gen (type Heap memory) = 715849728 (682.69 M)
Pool: Perm Gen (type Non-heap memory) = 174063616 (166.00 M)

Обратите внимание, что Eden + 2 * Survivor + Tenured = 1024M, что соответствует количеству кучи, запрошенному в командной строке. Большое спасибо @Absurd-Mind за это.

Различия, которые вы наблюдаете между разными JVM, вероятно, связаны с различной эвристикой для выбора относительных размеров по умолчанию для разных поколений. Как описано в в этой статье (применимо к Java 6, не удалось найти более недавнюю версию), вы можете использовать -XX:NewRatio и -XX:SurvivorRatio, чтобы явно управлять этими настройками. Итак, запустив команду:

java -Xmx1024m -XX:NewRatio=3 -XX:SurvivorRatio=6

Вы сообщаете JVM, что:

Young:Tenured = (Eden + 2*Survivor):Tenured = 1:3 = 256m:768m
Survivor:Eden = 1:6 = 32m:192m

Таким образом, с этими параметрами разница между запрошенным значением -Xmx и доступной памятью, сообщенной Runtime.maxMemory(), должна быть 32 м, что подтверждается с использованием вышеуказанной программы. И теперь вы должны иметь возможность точно предсказать доступную память, сообщенную Runtime для заданного набора аргументов командной строки, чего вы действительно хотели, правильно?

Ответ 2

На следующем графике показано Runtime.maxMemory в процентах от Xmx (по оси Y) для различных значений Xmx (по оси X).

Мы видим, что по большей части только ~ 85% настройки Xmx доступно для роста кучи. Этот анализ проводится с использованием Java-версии "1.8.0_212" Java (TM) SE Runtime Environment (сборка 1.8.0_212-b10) 64-разрядной серверной виртуальной машины Java HotSpot (TM) (сборка 25.212-b10, смешанный режим)

График (maxMem/Xmx) для разных размеров Xmx (в МБ)