Java получает доступную память

Есть ли хороший способ получить оставшуюся память в JVM во время выполнения? Предпосылкой для этого было бы иметь веб-службы, которые изящно терпят неудачу, когда они приблизятся к их ограничениям памяти, отказываясь от новых соединений с хорошим сообщением об ошибке "слишком много людей, использующих это, повторите попытку позже", вместо того, чтобы внезапно умереть с ошибкой OutOfMemory.

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

Ответ 1

Этот пример Уильяма Бренделя может быть полезен.

EDIT: Я изначально предоставил этот образец (ссылка на ответ Уильяма Бренделя по другой теме). Создатель этой темы (Стив М) хотел создать многоплатформенное Java-приложение. В частности, пользователь пытался найти средство, с помощью которого можно оценить текущие ресурсы машины (дисковое пространство, использование ЦП и памяти).

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

public class Main {
  public static void main(String[] args) {
  /* Total number of processors or cores available to the JVM */
  System.out.println("Available processors (cores): " + 
  Runtime.getRuntime().availableProcessors());

  /* Total amount of free memory available to the JVM */
  System.out.println("Free memory (bytes): " + 
  Runtime.getRuntime().freeMemory());

  /* This will return Long.MAX_VALUE if there is no preset limit */
  long maxMemory = Runtime.getRuntime().maxMemory();
  /* Maximum amount of memory the JVM will attempt to use */
  System.out.println("Maximum memory (bytes): " + 
  (maxMemory == Long.MAX_VALUE ? "no limit" : maxMemory));

  /* Total memory currently in use by the JVM */
  System.out.println("Total memory (bytes): " + 
  Runtime.getRuntime().totalMemory());

  /* Get a list of all filesystem roots on this system */
  File[] roots = File.listRoots();

  /* For each filesystem root, print some info */
  for (File root : roots) {
    System.out.println("File system root: " + root.getAbsolutePath());
    System.out.println("Total space (bytes): " + root.getTotalSpace());
    System.out.println("Free space (bytes): " + root.getFreeSpace());
    System.out.println("Usable space (bytes): " + root.getUsableSpace());
  }
 }
}

Пользователь Christian Fries отмечает, что неправильно предположить, что Runtime.getRuntime().freeMemory() дает вам объем памяти, который может быть выделен до тех пор, пока не произойдет ошибка вне памяти.

В документации возврат подписи Runtime.getRuntime().freeMemory() таков:

Возвращает: приблизительное значение общего объема памяти, доступного в настоящее время для будущих выделенных объектов, измеренного в байтах.

Однако пользователь Christian Fries утверждает, что эта функция может быть неверно истолкована. Он утверждает, что приблизительный объем памяти, который может быть выделен до возникновения ошибки вне памяти (свободная память), скорее всего, будет задаваться:

long presumableFreeMemory = Runtime.getRuntime().maxMemory() - allocatedMemory;

С allocatedMemory задается:

long allocatedMemory = 
  (Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory());

Ключевым моментом здесь является несоответствие между понятием свободной памяти. Одна вещь - это память, которую операционная система предоставляет виртуальной машине Java. Другим является общее количество байтов, содержащих куски блоков памяти, которые фактически используются самой виртуальной машиной Java.

Учитывая, что память, предоставленная Java-приложениям, управляется в блоках виртуальной машиной Java, объем свободной памяти, доступной для виртуальной машины Java, может не соответствовать памяти, доступной для Java-приложения.

В частности, Christian Fries обозначает использование флагов -mx или -Xmx для установки максимального объема памяти, доступного для виртуальной машины Java. Он отмечает следующие отличия в функции:

/* Returns the maximum amount of memory available to 
   the Java Virtual Machine set by the '-mx' or '-Xmx' flags. */
Runtime.getRuntime().maxMemory();

/* Returns the total memory allocated from the system 
   (which can at most reach the maximum memory value 
   returned by the previous function). */
Runtime.getRuntime().totalMemory();

/* Returns the free memory *within* the total memory 
   returned by the previous function. */
Runtime.getRuntime().freeMemory();

Кристиан заканчивает свой ответ тем, что Runtime.getRuntime().freeMemory() фактически возвращает то, что можно назвать предположительной свободной памятью; даже если последующее распределение памяти не превышает значение, возвращаемое этой функцией, если виртуальная машина Java еще не получила фактический кусок памяти, назначенный хост-системой, java.lang.OutOfMemoryError все еще можно создать.

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

Я предоставляю другую ссылку, которая может быть полезна. Речь идет о пользователе Ричарда Дорманда и ответил камнями333 об определении используемого размера кучи Java по умолчанию.

Ответ 2

Примечание. Все ответы до сих пор, даже принятые, похоже, отвечают на вопрос, говоря, что Runtime.getRuntime().freeMemory() дает вам объем памяти, который может быть выделен до возникновения ошибки вне памяти. Однако: это неправильно.

Примерный объем памяти, который может быть выделен до возникновения ошибки вне памяти, то есть, вероятно, свободная память

long presumableFreeMemory = Runtime.getRuntime().maxMemory() - allocatedMemory;

где

long allocatedMemory      = (Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory());

Объяснение: Если вы запускаете JVM через параметр -mx (или -Xmx), вы указываете максимальную сумму, доступную JVM. Runtime.getRuntime().maxMemory() предоставит вам эту сумму. Из этого объема системной памяти JVM будет выделять память кусками, например, блоков размером 64 Мб. При запуске JVM будет выделять только такой кусок из системы, а не полную сумму. Runtime.getRuntime().totalMemory() дает общую память, выделенную из системы, а Runtime.getRuntime().freeMemory() дает вам свободную память в выделенной общей памяти.

Следовательно:

long definitelyFreeMemory = Runtime.getRuntime().freeMemory();

- свободная память, уже зарезервированная JVM, но это, скорее всего, небольшая сумма. И вы, скорее всего, получите presumableFreeMemory. Конечно, вы можете получить исключение из памяти, даже если вы попытались выделить сумму меньше presumableFreeMemory. Это может произойти, если JVM не получит следующий блок памяти из системы. Однако в большинстве систем этого никогда не будет, и система скорее начнет замену - ситуация, которую вы хотите избежать. W.r.t. к исходному вопросу: если -mx установлен на разумное значение, то presumableFreeMemory является хорошим индикатором свободной памяти.

Ответ 3

В дополнение к использованию методов Runtime вы можете получить дополнительную информацию о памяти, используя

MemoryMXBean memBean = ManagementFactory.getMemoryMXBean();
MemoryUsage heap = memBean.getHeapMemoryUsage();
MemoryUsage nonheap = memBean.getNonHeapMemoryUsage();

Каждый MemoryUsage содержит значения init, used, commit и max. Это может быть полезно, если создать поток монитора памяти, который проверяет память и регистрирует ее, предоставляя вам историю использования памяти с течением времени. Иногда полезно видеть использование памяти с течением времени, приводящее к ошибкам.

Если вы действительно хотите сделать это до крайности, создайте поток дампа кучи. Мониторинг использования памяти с течением времени и превышение определенных пороговых значений делает следующее (это работает на JBoss 5.0 - ваш пробег может меняться):

// init code
MBeanServer server = ManagementFactory.getPlatformMBeanServer();
HotSpotDiagnosticMXBean diagBean = ManagementFactory.newPlatformMXBeanProxy(server, "com.sun.management:type=HotSpotDiagnostic", HotSpotDiagnosticMXBean.class); 

// loop code
// add some code to figure if we have passed some threshold, then

File heapFile = new File(outputDir, "heap-" + curThreshold + ".hprof");
log.info("Dumping heap file " + heapFile.getAbsolutePath());
diagBean.dumpHeap(heapFile.getAbsolutePath(), true);

Позже вы можете просмотреть эти файлы дампа кучи с помощью анализатора памяти eclipse или аналогичные инструменты для проверки утечек памяти и т.д.

Ответ 4

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

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

Ответ 5

Чтобы получить доступную для всей ОС память, добавьте OSHI, используя следующую зависимость maven:

<dependency>
    <groupId>com.github.oshi</groupId>
    <artifactId>oshi-core</artifactId>
    <version>LATEST</version>
</dependency>

Затем в Java используйте следующий код:

SystemInfo si = new SystemInfo();
HardwareAbstractionLayer hal = si.getHardware();
long availableMemory = hal.getMemory().getAvailable();

Ответ 6

Вы всегда можете позвонить Runtime.getRuntime().freeMemory().

Другая половина проблемы, получающая стоимость объектов, кажется мне более проблематичной.

Я думаю, что лучшим решением будет выяснение того, как кластер и масштабировать ваши веб-сервисы, чтобы они могли изящно принять 150% от номинальной нагрузки, не отказываясь от новых подключений. Звучит как упражнение для калибровки, вы получите лучшее решение, чем взлом кода.

Ответ 7

Runtime.getRuntime().freeMemory() - это способ получить свободную память для JVM в данный момент во время выполнения. Это хороший способ (или) не полностью зависит от вашего приложения.

Ответ 8

long freeMemory = Runtime.getRuntime().freeMemory();