Статьи

HotSpot по умолчанию Максимальный прямой объем памяти

В моем предыдущем сообщении в блоге « Улучшенная документация параметров HotSpot в Java 8» я писал о недоразумениях, связанных с настройкой по умолчанию в HotSpot JVM для нестандартного параметра -XX:MaxDirectMemorySize . В этой статье я рассмотрю простой способ определения максимального прямого объема памяти по умолчанию в JVM HotSpot.

В документации по Java 8 для средства запуска Java говорится следующее относительно -XX:MaxDirectMemorySize (я добавил ударение ):

Устанавливает максимальный общий размер (в байтах) распределения прямого буфера New I / O (пакет java.nio ). Добавьте буквы k или K для обозначения килобайт, m или M для обозначения мегабайт, g или G для обозначения гигабайт. По умолчанию размер равен 0, что означает, что JVM автоматически выбирает размер для прямого буфера NIO.

Вышеприведенное объясняет, что 0 является значением по умолчанию для максимального прямого объема памяти в HotSpot, если размер не указан явно с помощью -XX:MaxDirectMemorySize . Использование таких опций, как -XX: + PrintFlagsInitial и -XX: + PrintFlagsFinal , в этом случае не помогает, поскольку значения, которые будут отображаться, также равны нулю, если не указано явно. Например, при запуске java -XX:+PrintFlagsFinal -version отображается:

1
size_t MaxDirectMemorySize                       = 0

Насколько я знаю, не существует «стандартного» способа доступа к максимальному прямому объему памяти. Класс java.lang.Runtime предоставляет информацию о приблизительной свободной памяти в JVM , общем объеме памяти в JVM и максимальном объеме памяти, который JVM будет пытаться использовать . Хотя java.lang.management.MemoryMXBean предлагает использование памяти без кучи в дополнение к использованию памяти кучи , это использование без кучи относится к «области метода» и, возможно, к «внутренней обработке или оптимизации» реализации, а не к прямой памяти.

Существует несколько нестандартных подходов к определению максимального объема памяти HotSpot JVM по умолчанию. В потоке StackOverflow Есть ли способ измерить прямое использование памяти в Java? Вискиспайдер пишет о sun.misc.SharedSecrets.getJavaNioAccess (). getDirectBufferPool (). getMemoryUsed () ‌ и sun.misc.VM.maxDirectMemory () . Эти классы, специфичные для HotSpot, соответственно указывают объем используемой прямой памяти и максимальный объем используемой прямой памяти.

Класс sun.misc.SharedSecrets предоставляет информацию о прямом использовании памяти через вызовы getJavaNioAccess().getDirectBufferPool() для доступа к экземпляру sun.misc.JavaNioAccess.BufferPool . Интерфейс BufferPool определяет три метода, предоставляющих непосредственные детали, связанные с памятью: getCount() , getTotalCapacity() и getMemoryUsed() . Хотя эти методы предоставляют интересные подробности о прямом использовании памяти, они не говорят нам, каков максимальный объем прямой памяти.

Метод sun.misc.VM.maxDirectMemory () в JVM HotSpot предоставляет нам максимальную прямую память, независимо от того, была ли она явно указана с -XX:MaxDirectMemorySize= или была ли она неявно установлена ​​так, что -XX:MaxDirectMemorySize=0 (по умолчанию) и виртуальная машина выбирает максимальный размер прямой памяти.

Чтобы продемонстрировать использование этих методов для определения максимальной используемой прямой памяти и прямой памяти, я сначала представлю утилиту, которую буду использовать в моих примерах. Это enum называется MemoryUnit и адаптировано для этого поста из dustin.utilities.memory.MemoryUnit.java . Я мог бы использовать Apache Commons FileUtils.byteCountToDisplaySize (long) или более сложную адаптацию Бриса Макивера , но решил использовать это простое перечисление, вдохновленное TimeUnit, как показано ниже.

MemoryUnit.java

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
package dustin.examples.maxdirectmemory;
 
/**
 * Representation of basic memory units.
 */
public enum MemoryUnit
{
   /** Smallest memory unit. */
   BYTES,
   /** "One thousand" (1024) bytes. */
   KILOBYTES,
   /** "One million" (1024x1024) bytes. */
   MEGABYTES,
   /** "One billion" (1024x1024x1024) bytes. */
   GIGABYTES;
 
   /** Number of bytes in a kilobyte. */
   private final double BYTES_PER_KILOBYTE = 1024.0;
   /** Number of kilobytes in a megabyte. */
   private final double KILOBYTES_PER_MEGABYTE = 1024.0;
   /** Number of megabytes per gigabyte. */
   private final double MEGABYTES_PER_GIGABYTE = 1024.0;
 
   /**
    * Returns the number of bytes corresponding to the
    * provided input for a particular unit of memory.
    *
    * @param input Number of units of memory.
    * @return Number of bytes corresponding to the provided
    *    number of particular memory units.
    */
   public double toBytes(final long input)
   {
      double bytes;
      switch (this)
      {
         case BYTES:
            bytes = input;
            break;
         case KILOBYTES:
            bytes = input * BYTES_PER_KILOBYTE;
            break;
         case MEGABYTES:
            bytes = input * BYTES_PER_KILOBYTE * KILOBYTES_PER_MEGABYTE;
            break;
         case GIGABYTES:
            bytes = input * BYTES_PER_KILOBYTE * KILOBYTES_PER_MEGABYTE * MEGABYTES_PER_GIGABYTE;
            break;
         default :
            throw new RuntimeException("No value '" + this + "' recognized for enum MemoryUnit.");
      }
      return bytes;
   }
 
   /**
    * Returns the number of kilobytes corresponding to the
    * provided input for a particular unit of memory.
    *
    * @param input Number of units of memory.
    * @return Number of kilobytes corresponding to the provided
    *    number of particular memory units.
    */
   public double toKiloBytes(final long input)
   {
      double kilobytes;
      switch (this)
      {
         case BYTES:
            kilobytes = input / BYTES_PER_KILOBYTE;
            break;
         case KILOBYTES:
            kilobytes = input;
            break;
         case MEGABYTES:
            kilobytes = input * KILOBYTES_PER_MEGABYTE;
            break;
         case GIGABYTES:
            kilobytes = input * KILOBYTES_PER_MEGABYTE * MEGABYTES_PER_GIGABYTE;
            break;
         default:
            throw new RuntimeException("No value '" + this + "' recognized for enum MemoryUnit.");
      }
      return kilobytes;
   }
 
   /**
    * Returns the number of megabytes corresponding to the
    * provided input for a particular unit of memory.
    *
    * @param input Number of units of memory.
    * @return Number of megabytes corresponding to the provided
    *    number of particular memory units.
    */
   public double toMegaBytes(final long input)
   {
      double megabytes;
      switch (this)
      {
         case BYTES:
            megabytes = input / BYTES_PER_KILOBYTE / KILOBYTES_PER_MEGABYTE;
            break;
         case KILOBYTES:
            megabytes = input / KILOBYTES_PER_MEGABYTE;
            break;
         case MEGABYTES:
            megabytes = input;
            break;
         case GIGABYTES:
            megabytes = input * MEGABYTES_PER_GIGABYTE;
            break;
         default:
            throw new RuntimeException("No value '" + this + "' recognized for enum MemoryUnit.");
      }
      return megabytes;
   }
 
   /**
    * Returns the number of gigabytes corresponding to the
    * provided input for a particular unit of memory.
    *
    * @param input Number of units of memory.
    * @return Number of gigabytes corresponding to the provided
    *    number of particular memory units.
    */
   public double toGigaBytes(final long input)
   {
      double gigabytes;
      switch (this)
      {
         case BYTES:
            gigabytes = input / BYTES_PER_KILOBYTE / KILOBYTES_PER_MEGABYTE / MEGABYTES_PER_GIGABYTE;
            break;
         case KILOBYTES:
            gigabytes = input / KILOBYTES_PER_MEGABYTE / MEGABYTES_PER_GIGABYTE;
            break;
         case MEGABYTES:
            gigabytes = input / MEGABYTES_PER_GIGABYTE;
            break;
         case GIGABYTES:
            gigabytes = input;
            break;
         default:
            throw new RuntimeException("No value '" + this + "' recognized for enum MemoryUnit.");
      }
      return gigabytes;
   }
}

С MemoryUnit доступным в качестве вспомогательной утилиты, следующий пример кода демонстрирует использование методов JavaNioAccess.BufferPool, предоставляемых SharedSecrets . Эти значения не являются максимально возможной прямой памятью, а являются оценками уже используемой прямой памяти.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
/**
 * Write amount of direct memory used to standard output
 * using SharedSecrets, JavaNetAccess, the direct Buffer Pool,
 * and methods getMemoryUsed() and getTotalCapacity().
 */
public static void writeUsedDirectMemoryToStdOut()
{
   final double sharedSecretsMemoryUsed =
      MemoryUnit.BYTES.toMegaBytes(
         SharedSecrets.getJavaNioAccess().getDirectBufferPool().getMemoryUsed());
   out.println(
      "sun.misc.SharedSecrets.getJavaNioAccess().getDirectBufferPool().getMemoryUsed(): "
         + sharedSecretsMemoryUsed + " MB");
   final double sharedSecretsTotalCapacity =
      MemoryUnit.BYTES.toMegaBytes(SharedSecrets.getJavaNioAccess().getDirectBufferPool().getTotalCapacity());
   out.println("sun.misc.SharedSecrets.getJavaNioAccess().getDirectBufferPool().getTotalCapacity(): "
      + sharedSecretsTotalCapacity + " MB");
}

Приведенный выше код может быть выполнен после помещения чего-либо в прямую память с помощью строки, подобной следующей:

1
final ByteBuffer bytes = ByteBuffer.allocateDirect(1_000_000);

Когда используется прямая память, как показано выше, и код выше, который выполняется, вывод выглядит следующим образом:

1
2
sun.misc.SharedSecrets.getJavaNioAccess().getDirectBufferPool().getMemoryUsed(): 0.95367431640625 MB
sun.misc.SharedSecrets.getJavaNioAccess().getDirectBufferPool().getTotalCapacity(): 0.95367431640625 MB

Только что продемонстрированные методы дают оценку объема используемой прямой памяти, но все же не показывают максимально доступную прямую память. Это можно определить с помощью VM.maxDirectMemory как показано в следующем листинге кода.

01
02
03
04
05
06
07
08
09
10
11
12
/**
 * Write maximum direct memory size set (explicitly or
 * implicitly) for this VM instance using VM's
 * method maxDirectMemory().
 */
public static void writeMaximumDirectMemorySizeToStdOut()
{
   final double vmSize =
      MemoryUnit.BYTES.toMegaBytes(VM.maxDirectMemory());
   out.println(
       "sun.misc.VM.maxDirectMemory(): " + vmSize + " MB");
}

Когда приведенный выше код выполняется на моем ноутбуке с JDK 8 и явно не указан -XX:MaxDirectMemorySize , результат выглядит следующим образом:

1
sun.misc.VM.maxDirectMemory(): 1804.5 MB

Исходя из этого, я вижу, что JVM, работающий на моей машине, имеет максимальный размер прямой памяти по умолчанию приблизительно 1,8 ГБ. Я знаю, что это значение по умолчанию, потому что я не указал явно -XX:MaxDirectMemorySize в командной строке и потому что при запуске примера Java-приложения с -XX: + PrintFlagsFinal показывает ноль (по умолчанию) для него.

Чтобы убедиться, что этот подход показывает правильную максимальную прямую память, я могу явно указать максимальную прямую память в командной строке и посмотреть, что выдает код, показанный выше. В этом случае я предоставляю -XX:MaxDirectMemorySize=3G в командной строке. Вот вывод, когда я запускаю приведенный выше код с этой явной настройкой:

1
sun.misc.VM.maxDirectMemory(): 3072.0 MB

Вывод

Когда нужно знать максимальную прямую память, доступную для конкретного приложения, работающего на JVM HotSpot, метод VM.maxDirectMemory() — это, вероятно, самый простой способ получить эту информацию, если -XX:MaxDirectMemorySize явно не указан. Знание максимально допустимой прямой памяти может быть полезно при работе непосредственно с Java NIO или даже при косвенной работе с Java NIO при работе с продуктами, использующими Java NIO, такими как терракотовые и Hazelcast опции «offheap».