#java #jvm #64-bit #jvm-arguments
#java #jvm #64-разрядная #jvm-аргументы
Вопрос:
Я борюсь с большими требованиями к памяти для java-приложения.
Чтобы адресовать больше памяти, я переключился на 64-разрядную JVM и использую большой xmx. Однако, когда xmx превышает 2 ГБ, приложение, похоже, исчерпывает память раньше, чем ожидалось. При запуске с xmx 2400M и просмотре информации GC -verbosegc
, которую я получаю…
[Full GC 2058514K->2058429K(2065024K), 0.6449874 secs]
… и затем он выдает исключение нехватки памяти. Я бы ожидал, что это увеличит кучу выше 2065024 КБ, прежде чем закончится память.
В тривиальном примере у меня есть тестовая программа, которая выделяет память в цикле и выводит информацию из Runtime.getRuntime().maxMemory()
и Runtime.getRuntime().totalMemory()
до тех пор, пока в конечном итоге не закончится память.
Запуск этого по диапазону значений xmx показывает, что Runtime.getRuntime().maxMemory()
отчеты примерно на 10% меньше, чем xmx, и что общая память не будет увеличиваться более чем на 90% Runtime.getRuntime().maxMemory()
. .
Я использую следующую 64-битную jvm:
версия java "1.6.0_26" Среда выполнения Java (TM) SE (сборка 1.6.0_26-b03) 64-разрядная серверная виртуальная машина Java HotSpot (TM) (сборка 20.1-b02, смешанный режим)
Вот код:
import java.util.ArrayList;
public class XmxTester {
private static String xmxStr;
private long maxMem;
private long usedMem;
private long totalMemAllocated;
private long freeMem;
private ArrayList list;
/**
* @param args
*/
public static void main(String[] args) {
xmxStr = args[0];
XmxTester xmxtester = new XmxTester();
}
public XmxTester() {
byte[] mem = new byte[(1024 * 1024 * 50)];
list = new ArrayList();
while (true) {
printMemory();
eatMemory();
}
}
private void eatMemory() {
// TODO Auto-generated method stub
byte[] mem = null;
try {
mem = new byte[(1024 * 1024)];
} catch (Throwable e) {
System.out.println(xmxStr "," ConvertMB(maxMem) ","
ConvertMB(totalMemAllocated) "," ConvertMB(usedMem)
"," ConvertMB(freeMem));
System.exit(0);
}
list.add(mem);
}
private void printMemory() {
maxMem = Runtime.getRuntime().maxMemory();
freeMem = Runtime.getRuntime().freeMemory();
totalMemAllocated = Runtime.getRuntime().totalMemory();
usedMem = totalMemAllocated - freeMem;
}
double ConvertMB(long bytes) {
int CONVERSION_VALUE = 1024;
return Math.round((bytes / Math.pow(CONVERSION_VALUE, 2)));
}
}
Я использую этот пакетный файл для запуска его с несколькими настройками xmx. Он включает ссылки на 32-разрядную JVM, я хотел сравнить с 32-разрядной jvm — очевидно, что этот вызов завершается неудачей, как только xmx превышает примерно 1500 М.
@echo off
set java64=<location of 64bit JVM>
set java32=<location of 32bit JVM>
set xmxval=64
:start
SET /a xmxval = %xmxval% 64
%java64% -Xmx%xmxval%m -XX: UseCompressedOops -XX: DisableExplicitGC XmxTester %xmxval%
%java32% -Xms28m -Xmx%xmxval%m XmxTester %xmxval%
if %xmxval% == 4500 goto end
goto start
:end
pause
Это выдает csv, который при вводе в Excel выглядит следующим образом (прошу прощения за мое плохое форматирование здесь)
32 бит
XMX max mem всего mem свободного mem% от xmx, используемого до исключения из mem 128 127 127 125 2 98.4% 192 191 191 189 1 99.0% 256 254 254 252 2 99.2% 320 318 318 316 1 99.4% 384 381 381 379 2 99.5% 448 445 445 443 1 99.6% 512 508 508 506 2 99.6% 576 572 572 570 1 99.7% 640 635 635 633 2 99.7% 704 699 699 697 1 99.7% 768 762 762 760 2 99.7% 832 826 826 824 1 99.8% 896 889 889 887 2 99.8% 960 953 953 952 0 99.9% 1024 1016 1016 1014 2 99.8% 1088 1080 1080 1079 1 99.9% 1152 1143 1143 1141 2 99.8% 1216 1207 1207 1205 2 99.8% 1280 1270 1270 1268 2 99.8% 1344 1334 1334 1332 2 99.9%
64 бит
128 122 122 116 6 90.6% 192 187 187 180 6 93.8% 256 238 238 232 6 90.6% 320 285 281 275 6 85.9% 384 365 365 359 6 93.5% 448 409 409 402 6 89.7% 512 455 451 445 6 86.9% 576 512 496 489 7 84.9% 640 595 595 565 30 88.3% 704 659 659 629 30 89.3% 768 683 682 676 6 88.0% 832 740 728 722 6 86.8% 896 797 772 766 6 85.5% 960 853 832 825 6 85.9% 1024 910 867 860 7 84.0% 1088 967 916 909 6 83.5% 1152 1060 1060 1013 47 87.9% 1216 1115 1115 1068 47 87.8% 1280 1143 1143 1137 6 88.8% 1344 1195 1174 1167 7 86.8% 1408 1252 1226 1220 6 86.6% 1472 1309 1265 1259 6 85.5% 1536 1365 1317 1261 56 82.1% 1600 1422 1325 1318 7 82.4% 1664 1479 1392 1386 6 83.3% 1728 1536 1422 1415 7 81.9% 1792 1593 1455 1448 6 80.8% 1856 1650 1579 1573 6 84.8% 1920 1707 1565 1558 7 81.1% 1984 1764 1715 1649 66 83.1% 2048 1821 1773 1708 65 83.4% 2112 1877 1776 1769 7 83.8% 2176 1934 1842 1776 66 81.6% 2240 1991 1899 1833 65 81.8% 2304 2048 1876 1870 6 81.2% 2368 2105 1961 1955 6 82.6% 2432 2162 2006 2000 6 82.2%
Комментарии:
1. 32-разрядная JVM завершается сбоем только при 1500 МБ на некоторых аппаратных платформах.
Ответ №1:
Почему это происходит?
В принципе, есть две стратегии, которые JVM / GC может использовать, чтобы решить, когда отказаться и бросить OOME.
- Это может продолжаться и продолжаться до тех пор, пока после сборки мусора просто не останется достаточно памяти для выделения следующего объекта.
- Это может продолжаться до тех пор, пока JVM не будет тратить больше заданного процента времени на сборщик мусора.
Первый подход имеет проблему, заключающуюся в том, что для типичного приложения JVM будет тратить все больший и больший процент своего времени на выполнение GC, в конечном итоге тщетно пытаясь выполнить задачу.
Второй подход имеет проблему, заключающуюся в том, что он может сдаться слишком рано.
Фактическое поведение GC в этой области определяется параметрами JVM (-XX:…). По-видимому, поведение по умолчанию отличается между 32 и 64-разрядными JVM. Это имеет смысл, потому что (интуитивно) эффект «спирали смерти памяти» для 64-разрядной JVM будет длиться дольше и будет более выраженным.
Мой совет — оставить эту проблему в покое. Если вам действительно не нужно заполнять каждый последний байт памяти материалом, для JVM лучше умереть раньше и не тратить много времени. Затем вы можете перезапустить его с большим объемом памяти и выполнить работу.
Очевидно, что ваш тест нетипичен. Большинство реальных программ просто не пытаются захватить всю кучу. Возможно, ваше приложение тоже нетипично. Но также возможно, что ваше приложение страдает от утечки памяти. Если это так, вам следует расследовать утечку, а не пытаться выяснить, почему вы не можете использовать всю память.
Однако моя проблема в основном связана с тем, почему она не учитывает мои настройки xmx.
Это честь! -Xmx — это верхний предел размера кучи, а не критерий для принятия решения о том, когда отказаться.
Я установил XMX равным 2432 МБ, но просьба JVM вернуть свое понимание максимальной памяти возвращает 2162 МБ.
Он возвращает максимальную память, которую он использовал, а не максимальную память, которую ему разрешено использовать.
Почему он «думает», что максимальная память на 11% меньше, чем у xmx?
Смотрите выше.
Кроме того, почему, когда куча достигает 2006M, она не расширяет кучу как минимум до 2162?
Я предполагаю, что это связано с тем, что JVM достигла порога «слишком много времени, потраченного на сбор мусора».
Означает ли это, что в 64-разрядных JVM следует изменить настройку XMX на 11% выше предполагаемого максимума?
В общем, нет. Коэффициент выдумки зависит от вашего приложения. Например, приложение с большей скоростью оттока объектов (т. Е. Больше объектов создается и отбрасывается на единицу полезной работы), скорее всего, умрет с OOME раньше.
Я могу предсказать требования на основе размера БД и иметь оболочку, которая настраивает xmx, однако у меня есть проблема 11%, из-за которой мой монтирование предполагает, что приложению требуется 2 ГБ, поэтому я установил xmx 2,4 ГБ. однако вместо ожидаемых 400 МБ «запаса», jvm позволяет куче расти только до 2006M.
IMO, решение состоит в том, чтобы просто добавить дополнительные 20% (или больше) поверх того, что вы добавляете в данный момент. Предполагая, что у вас достаточно физической памяти, предоставление JVM большей кучи уменьшит общие накладные расходы на сборку и ускорит работу вашего приложения.
Другие трюки, которые вы могли бы попробовать, — это установить -Xmx и -Xms на одно и то же значение и настроить параметр настройки, который устанавливает максимальное соотношение «время, затраченное на сбор мусора».
Комментарии:
1. Спасибо, Стивен, это имеет смысл в отношении того, что 64-разрядная JVM «отказывается» от 32-разрядной JVM. Однако моя проблема в основном связана с тем, почему она не учитывает мои настройки xmx. В примере 2,4 ГБ. Я установил XMX равным 2432 МБ, но просьба JVM вернуть свое понимание максимальной памяти возвращает 2162 МБ. Почему он «думает», что максимальная память на 11% меньше, чем у xmx? Кроме того, почему, когда куча достигает 2006M, она не расширяет кучу как минимум до 2162? Означает ли это, что в 64-разрядных JVM следует изменить настройку XMX на 11% выше предполагаемого максимума?
2. Я ценю вашу точку зрения об утечках памяти, моя тестовая программа, очевидно, представляет собой утечку памяти — выделил ряд массивов размером 1 МБАЙТ в список … Однако реальная ситуация — это приложение поверх базы данных Java в памяти, по мере роста базы данных требования к памяти возрастают. Я могу предсказать требования на основе размера БД и иметь оболочку, которая настраивает xmx, однако у меня есть проблема 11%, из-за которой мой монтирование предполагает, что приложению требуется 2 ГБ, поэтому я установил xmx 2,4 ГБ. однако вместо ожидаемого «запаса» в 400 МБ, jvm позволяет куче расти только до 2006M
3. Хорошо, я думаю, теперь я понимаю, теоретически использование памяти увеличится до максимального объема памяти, но, учитывая скорость оттока объектов, GC (в соответствии с вашим вторым вариантом выше) решает, что продолжать бессмысленное упражнение, поэтому сдается и выдает исключение нехватки памяти. Спасибо за помощь. Я согласен, что запас прочности в 20% решит мои проблемы. Также спасибо за совет xms = xmx — это, похоже, позволяет куче увеличиваться до того, как gc сдастся