Java: почему используется фиксированный объем памяти? или как он управляет памятью?

Кажется, что JVM использует некоторый фиксированный объем памяти. По крайней мере, я часто видел параметры -Xmx (для максимального размера) и -Xms (для начального размера), которые предполагают, что.

У меня было ощущение, что приложения Java не очень хорошо обращайтесь с памятью. Некоторые вещи, которые я заметил:

  • Даже некоторые очень маленькие демонстрационные приложения загружают огромные объемы памяти. Может быть, это из-за загруженной библиотеки Java. Но зачем загружать библиотеку для каждого экземпляра Java? (Кажется, что так, потому что несколько небольших приложений линейно занимают больше памяти. См. здесь для некоторых деталей, где я описываю эту проблему.) Или почему это делается таким образом?

  • Большие Java-приложения, такие как Eclipse, часто аварийно завершают работу. с некоторым исключением OutOfMemory. Это всегда было странно, потому что в моей системе оставалось достаточно памяти. Часто они потребляют все больше и больше памяти во время выполнения. Я не уверен, есть ли у них утечки памяти или это происходит из-за фрагментации в пуле памяти - у меня сложилось впечатление, что это последний случай.

  • Библиотека Java, кажется, требует гораздо больше памяти, чем аналогичные мощные библиотеки, такие как Qt, например. Почему это? (Для сравнения запустите некоторые приложения Qt, посмотрите на использование их памяти и запустите некоторые приложения Java. )

Почему он не использует только базовую системную технику, такую ​​как malloc и free ? Или, если им не нравится реализация libc, они могут использовать jemalloc (как в FreeBSD и Firefox ), что, похоже, довольно хорошо. Я совершенно уверен, что это будет работать лучше, чем пул памяти JVM. И не только лучше работать, но и требует меньше памяти, особенно. для небольших приложений.


Дополнение: Кто-нибудь уже пробовал это? Я был бы очень заинтересован в основанном на LLVM JIT-компиляторе для Java, который просто использует malloc / free для обработки памяти.

Или, может быть, это также отличается от реализации JVM для реализации? Я использовал в основном Sun JVM.

(Также обратите внимание: я не говорю прямо о GC здесь. ГХ отвечает только за то, чтобы рассчитать, какие объекты могут быть удалены, и за инициализацию освобождения памяти, но фактическое освобождение - это другая подсистема. Afaik, это какая-то собственная реализация пула памяти, а не просто вызов free .)


Редактирование: Очень связанный вопрос: Почему (Sun) JVM имеет фиксированный верхний предел для использование памяти? Или, иначе говоря: почему JVM обрабатывает выделение памяти не так, как собственные приложения?

12
задан Community 23 May 2017 в 12:00
поделиться

7 ответов

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

Незамедлительное знание того, где есть свободная память, делает выделение новых объектов молодому поколению эффективным и предотвращает необходимость бегать туда и обратно в базовую ОС. По словам Джона Масамицу из Sun, компилятор JIT также оптимизирует такие распределения вне уровня JVM:

Распределение быстрого пути не вызывает в JVM для размещения объекта. Компиляторы JIT умеют выделять из молодого поколения и кода для распределения генерируется в режиме реального времени для размещения объекта. Переводчик также умеет распределять без вызова виртуальной машины.

Обратите внимание, что JVM делает все возможное, чтобы попытаться получить большие непрерывные блоки памяти, которые, вероятно, имеют собственные преимущества в производительности (см. «Цена отсутствия кэша»).Я полагаю, что вызовы malloc (или альтернативы) имеют ограниченную вероятность предоставления непрерывной памяти между вызовами, но, возможно, я что-то упустил.

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

Выделение этих блоков памяти также обеспечивает защиту программы Java. Поскольку сборка мусора скрыта от программиста, они не могут сказать JVM: «Нет, оставьте эту память; я закончил с этими объектами, но мне нужно место для новых». Сохраняя память, GC не рискует отказаться от памяти, которую он не сможет вернуть. Естественно, вы всегда можете получить исключение OutOfMemoryException в любом случае, но кажется более разумным не возвращать память без нужды обратно в операционную систему каждый раз, когда вы закончите работу с объектом, поскольку вы уже потрудились получите это для себя.

Помимо всего этого, я постараюсь напрямую ответить на некоторые из ваших комментариев:

Часто они потребляют все больше и больше память во время выполнения.

Предполагая, что это не просто то, что делает программа (по какой-то причине, может быть, у нее есть утечка, может быть, она должна отслеживать увеличивающийся объем данных), я полагаю, что это связано с бесплатным По умолчанию соотношение хэш-пространства устанавливается JVM (Sun / Oracle).Значение по умолчанию для -XX: MinHeapFreeRatio составляет 40%, а -XX: MaxHeapFreeRatio - 70%. Это означает, что каждый раз, когда остается только 40% пространства кучи, размер кучи будет изменен, потребовав от операционной системы больше памяти (при условии, что это не превышает -Xmx ). И наоборот, он освободит * динамическую память обратно операционной системе только в том случае, если свободное пространство превышает 70%.

Подумайте, что произойдет, если я запустил память -интенсивная работа в Eclipse; профилирование, например. Мое потребление памяти будет расти, меняя размер кучи (вероятно, несколько раз) по пути. Как только я закончу, требования к памяти снова уменьшатся, но, скорее всего, не упадут так далеко, что 70% кучи станет свободным. Это означает, что теперь выделено много недостаточно используемого пространства, которое JVM не собирается освобождать. Это серьезный недостаток, но вы можете обойти его, настроив процентное соотношение в соответствии с вашей ситуацией. Чтобы получить лучшее представление об этом, вам действительно следует профилировать свое приложение, чтобы вы могли видеть используемое и выделенное пространство кучи. Я лично использую YourKit , но есть много хороших альтернатив на выбор.

* Я не знаю, действительно ли это время только и как это наблюдается с точки зрения ОС, но в документации сказано , что это «максимальный процент от без кучи после сборки мусора до , чтобы избежать сжатия », что, кажется, предполагает это.

Даже небольшая демонстрационная версия приложения загружают огромное количество объем памяти.

Думаю, это зависит от того, какие это приложения. Я чувствую, что приложения с графическим пользовательским интерфейсом Java требуют много памяти, но у меня нет никаких доказательств того или иного. У вас есть конкретный пример, который мы могли бы рассмотреть?

Но зачем загружать библиотеки для каждого экземпляра Java?

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

В заключение, медленное время запуска, о котором вы спрашивали в другом вопросе, вероятно, связано с несколькими начальными перераспределениями кучи, необходимыми для удовлетворения базовых требований к памяти приложения (из-за -Xms и - XX: MinHeapFreeRatio ), в зависимости от значений по умолчанию для вашей JVM.

15
ответ дан 2 December 2019 в 04:52
поделиться

Java работает внутри виртуальной машины, что ограничивает многие части ее поведения. Обратите внимание на термин «виртуальная машина». Он буквально работает так, как если бы машина - это отдельный объект, а базовая машина / ОС - это просто ресурсы. Значение -Xmx определяет максимальный объем памяти, который будет иметь виртуальная машина, а -Xms определяет начальную память, доступную для приложения.

ВМ является продуктом двоичного кода, который не зависит от системы - это было решение, позволяющее байтовому коду выполняться где угодно. Это похоже на эмулятор - скажем, для старых игровых систем. Это имитация «машины», на которой работает игра.

Причина возникновения OutOfMemoryException заключается в том, что виртуальная машина достигла предела -Xmx - ей буквально не хватает памяти.

Что касается небольших программ, им часто требуется больший процент памяти для виртуальной машины. Кроме того, у Java есть начальные значения -Xmx и -Xms по умолчанию (я не помню, какие они сейчас), с которых он всегда будет начинаться. Накладные расходы на виртуальную машину и библиотеки становятся гораздо менее заметными, когда вы начинаете создавать и запускать «настоящие» приложения.

Аргумент памяти, связанный с QT и т.п., верен, но это еще не все. Хотя он использует больше памяти, чем некоторые из них, они скомпилированы для определенных архитектур. Прошло некоторое время с тех пор, как я использовал QT или аналогичные библиотеки, но я помню, что управление памятью было не очень надежным, а утечки памяти все еще распространены сегодня в программах C / C ++. Приятная вещь в сборке мусора заключается в том, что она устраняет многие из распространенных ошибок, вызывающих утечки памяти. (Примечание: не все из них. Утечка памяти в Java все еще очень возможна, только немного сложнее).

Надеюсь, это поможет прояснить некоторые из ваших затруднений.

7
ответ дан 2 December 2019 в 04:52
поделиться

Java действительно использует malloc и free, или по крайней мере реализации JVM могут. Но поскольку Java отслеживает выделения и собирает мусор недоступных объектов, их явно недостаточно.

Что касается остальной части вашего текста, я не уверен, что там есть вопрос.

2
ответ дан 2 December 2019 в 04:52
поделиться

Даже некоторые очень маленькие демонстрационные приложения загружают огромные объемы памяти. Возможно, это из-за загруженной библиотеки Java. Но зачем загружать библиотеку для каждого экземпляра Java? (Кажется, так, потому что несколько небольших приложений линейно занимают больше памяти. См. Здесь некоторые подробности, где я описываю эту проблему.) Или почему это делается именно так?

Вероятно, это связано с накладными расходами на запуск и запуск JVM

Большие Java-приложения, такие как Eclipse, часто дают сбой из-за некоторого исключения OutOfMemory. Это всегда было странно, потому что в моей системе было достаточно памяти. Часто они потребляют все больше и больше памяти во время выполнения. Я не уверен, есть ли у них утечки памяти или это из-за фрагментации в пуле памяти - у меня такое ощущение, что последнее имеет место.

Я не совсем уверен, что вы имеете в виду под «частыми сбоями», поскольку не думаю, что со мной такое случалось достаточно давно. Если это так, скорее всего, это связано с настройкой «максимального размера», о которой вы говорили ранее.

Ваш главный вопрос, почему Java не использует malloc и free , зависит от целевого рынка. Java была разработана, чтобы избавить разработчика от головной боли, связанной с управлением памятью. Сборщик мусора Java достаточно хорошо справляется с освобождением памяти, когда ее можно освободить, но Java не предназначена для конкуренции с C ++ в ситуациях с ограничениями памяти.Java хорошо выполняет то, что было задумано (удаляет управление памятью на уровне разработчика), а JVM берет на себя ответственность достаточно хорошо, чтобы этого было достаточно для большинства приложений.

2
ответ дан 2 December 2019 в 04:52
поделиться

Чтобы ответить на часть вашего вопроса:

Java при запуске выделяет «кучу» памяти или блок фиксированного размера (параметр -Xms). На самом деле он не использует всю эту память сразу, но сообщает ОС: «Мне нужно столько памяти». Затем, когда вы создаете объекты и работаете в среде Java, он помещает созданные объекты в эту кучу предварительно выделенной памяти. Если этот блок памяти заполнится, он запросит у ОС немного больше памяти, пока не будет достигнут «максимальный размер кучи» (параметр -Xmx).

По достижении этого максимального размера Java больше не будет запрашивать у ОС больше оперативной памяти, даже если ее много. Если вы попытаетесь создать больше объектов, не останется места в куче, и вы получите исключение OutOfMemory. Теперь, если вы смотрите на Диспетчер задач Windows или что-то в этом роде, вы увидите «java.exe», использующий X мегабайт памяти. Этот вид соответствует объему памяти, который он запросил для кучи, а не фактически используемому объему памяти внутри кучи.

Другими словами, я мог бы написать приложение:

class myfirstjavaprog
{  
    public static void main(String args[])
    {
       System.out.println("Hello World!");
    }
}

которое в основном занимало бы очень мало памяти. Но если я запустил его со строкой cmd:

java.exe myfirstjavaprog -Xms 1024M

, то при запуске java немедленно запросит у ОС 1024 МБ оперативной памяти, и это то, что отобразится в диспетчере задач Windows. На самом деле этот баран не используется, но Java зарезервировал его для дальнейшего использования.

И наоборот, если бы у меня было приложение, которое пыталось создать большой массив размером 10 000 байт:

class myfirstjavaprog
{  
    public static void main(String args[])
    {
       byte[] myArray = new byte[10000];
    }
}

, но запускало его с помощью командной строки:

java.exe myfirstjavaprog -Xms 100 -Xmx 100

Тогда Java могла бы выделить только до 100 байт памяти.Поскольку массив размером 10 000 байт не помещается в 100-байтовую кучу, это вызовет исключение OutOfMemory, даже если в ОС много оперативной памяти.

Надеюсь, это имеет смысл ...


Редактировать:

Возвращаясь к «почему Java использует так много памяти»; как вы думаете, почему он использует много памяти? Если вы смотрите на то, что сообщает ОС, то это не то, что она фактически использует, а только то, что она зарезервирована для использования. Если вы хотите знать, что на самом деле использовала java, вы можете сделать дамп кучи и изучить каждый объект в куче и посмотреть, сколько памяти он использует.

Чтобы ответить «почему она просто не позволяет ОС справиться с этим?», Я полагаю, что это просто фундаментальный вопрос Java для тех, кто ее разработал. Как я на это смотрю; Java работает в JVM, которая представляет собой виртуальную машину. Если вы создаете экземпляр VMWare или любую другую «виртуализацию» системы, вам обычно необходимо указать, сколько памяти эта виртуальная система будет / может потреблять. Я считаю, что JVM похожа. Кроме того, эта модель абстрактной памяти позволяет JVM для разных ОС действовать одинаково. Так, например, Linux и Windows имеют разные модели распределения ОЗУ, но JVM может абстрагироваться от этого и следить за тем же использованием памяти для разных ОС.

3
ответ дан 2 December 2019 в 04:52
поделиться

Ограничения - это преднамеренное дизайнерское решение Sun. Я видел по крайней мере две другие JVM, которые не имеют такого дизайна - Microsoft и IBM для их не ПК систем AS/400. Обе растут по мере необходимости, используя столько памяти, сколько нужно.

2
ответ дан 2 December 2019 в 04:52
поделиться

Java не использует фиксированный размер памяти, он всегда находится в диапазоне от -Xms до -Xmx.

Если Eclipse завершает работу с ошибкой OutOfMemoryError, это означает, что ему потребовалось больше памяти, чем предоставлено параметром -Xmx (проблема конфигурации).

Java не должна использовать malloc/free (для создания объектов), поскольку ее обработка памяти значительно отличается из-за сборки мусора (GC). GC автоматически удаляет неиспользуемые объекты, что является преимуществом по сравнению с необходимостью отвечать за управление памятью.

Подробнее об этой сложной теме читайте в Tuning Garbage Collection

0
ответ дан 2 December 2019 в 04:52
поделиться
Другие вопросы по тегам:

Похожие вопросы: