Почему JVM (Sun) имеет фиксированный верхний предел для использования памяти (-Xmx)?

В духе вопроса Java: Почему существует MaxPermSize?, я хотел бы спросить, почему Sun JVM использует фиксированный верхний предел для размера своего пул распределения памяти.

По умолчанию используется 1/4 физической памяти (с верхним и нижним пределом); как следствие, если у вас есть приложение, зависящее от памяти, вам нужно вручную изменить лимит (параметр -Xmx), или ваше приложение будет работать плохо, возможно даже сбой с помощью OutOfMemoryError.

Почему этот фиксированный предел существует? Почему JVM не выделяет память по мере необходимости, например, в большинстве операционных систем?

Это решило бы целый класс общих проблем с программным обеспечением Java (просто Google, чтобы узнать, сколько намеков есть в сети при решении проблем, установив -Xmx).

Edit:

В некоторых ответах указывается, что это защитит остальную систему от программы Java с утечкой памяти; без ограничения это приведет к тому, что вся система будет исчерпана всей памятью. Это верно, однако, это одинаково верно для любой другой программы, и современные ОС уже позволяют ограничить максимальную память для программы (Linux ulimit, Windows "Объекты задания" ). Поэтому на самом деле это не отвечает на вопрос: "Почему JVM делает это по-другому от большинства других программ/сред среды выполнения?".

Ответ 1

Hm, я постараюсь обобщить ответы до сих пор.

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

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

Sun может опустить эту функцию и просто попросила людей использовать механизмы ограничения ресурсов на основе ОС, но они, вероятно, всегда хотели иметь предел, поэтому они внедрили ее сами. Во всяком случае, JVM должен знать любой такой предел (адаптировать свою стратегию GC), поэтому использование собственного механизма OS не спасло бы много усилий по программированию.

Кроме того, есть одна причина, по которой такой встроенный предел более важен для JVM, чем для "нормальной" программы без GC (например, программы C/С++):

В отличие от программы с ручным управлением памятью, программа, использующая GC, на самом деле не имеет четко определенного требования к памяти, даже с фиксированными входными данными. Он имеет только минимальное требование, то есть сумму размеров всех объектов, которые на самом деле живут (достижимы) в данный момент времени. Однако на практике программе потребуется дополнительная память для хранения мертвых, но еще не объектов GCed, потому что GC не может сразу собрать каждый объект, так как это вызовет слишком много накладных расходов GC. Поэтому GC время от времени запускается, и поэтому в куче требуется какая-то "передышка", где мертвые объекты могут ждать GC.

Это означает, что память, требуемая для программы с использованием GC, действительно является компромиссом между сохранением памяти и хорошей пропускной способностью (позволяя GC работать менее часто). Поэтому в некоторых случаях может быть разумным установить ограничение кучи ниже того, что будет использовать JVM, если это возможно, поэтому сэкономить RAM за счет производительности. Для этого должен быть способ установить ограничение кучи.

Ответ 2

Почему этот фиксированный предел существует? Почему JVM не выделяет память по мере необходимости, например, в большинстве операционных систем?

Причина НЕ, которую GC должен знать перед тем, что может быть максимальным размером кучи. JVM явно способен расширять свою кучу... до максимума... и я уверен, что было бы относительно небольшим изменением, чтобы удалить этот максимум. (В конце концов, другие реализации Java делают это.) И также было бы возможно иметь простой способ сказать "использовать столько памяти, сколько вам нравится" для JVM.

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

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

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

Таким образом, наличие комбинации кучи по умолчанию - это хорошо, потому что:

  • он защищает здоровье вашей системы,
  • это побуждает разработчиков/пользователей думать об использовании памяти "голодными" приложениями и
  • он потенциально позволяет оптимизировать GC. (Как было предложено другими ответами: это правдоподобно, но я не могу подтвердить это.)

ИЗМЕНИТЬ

В ответ на комментарии:

  • На самом деле не имеет значения, почему Sun JVM живут в ограниченной куче, где нет других приложений. Они делают, и преимущества этого - (ИМО) ясны. Возможно, более интересный вопрос заключается в том, почему другие управляемые языки по умолчанию не кладут привязку на свои кучи.

  • Подходы -Xmx и ulimit качественно отличаются. В первом случае JVM полностью знает пределы, в которых он работает, и получает возможность управлять его использованием памяти соответственно. В последнем случае первое, что типичное приложение C знает об этом, - это когда вызов malloc завершается с ошибкой. Типичным ответом является выход с кодом ошибки (если программа проверяет результат malloc) или умереть с ошибкой сегментации. Хорошо, приложение C может теоретически отслеживать, сколько памяти оно использовало, и попытаться ответить на надвигающийся кризис памяти. Но это будет тяжелая работа.

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

  • Интересна идея JVM, отвечающая "адаптивно" на запросы ОС, чтобы вернуть память. Но есть БОЛЬШАЯ проблема. Чтобы вернуть сегмент памяти, JVM сначала должен очистить любые доступные объекты в сегменте. Обычно это означает запуск сборщика мусора. Но запуск сборщика мусора - это последнее, что вы хотите сделать, если система находится в кризисе памяти... потому что в значительной степени гарантирована генерация пакета подкачки виртуальной памяти.

Ответ 3

Я думаю, что часть его связана с реализацией сборщика мусора (GC). GC обычно ленив, то есть он только начнет действительно пытаться восстановить память внутри, когда куча имеет максимальный размер. Если вы не установили верхний предел, время выполнения с радостью продолжит раздуваться до тех пор, пока он не будет использовать каждый доступный бит памяти в вашей системе.

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

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

Ответ 4

Это связано с дизайном JVM. Другие JVM (такие как Microsoft и некоторые IBM) могут использовать всю имеющуюся в системе память, если это необходимо, без каких-либо ограничений.

Я считаю, что это позволяет GC-оптимизации.

Ответ 5

Я думаю, что верхний предел для памяти связан с тем, что JVM является виртуальной машиной.

Так как любая физическая машина имеет заданное (фиксированное) количество ОЗУ, то VM имеет один.

Максимальный размер упрощает управление JVM операционной системой и обеспечивает некоторую прирост производительности (без замены).

Sun 'JVM также работает в довольно ограниченной аппаратной архитектуре (встроенные ARM-системы), и там важно управление ресурсами.

Ответ 6

Разве не было бы смысла разграничивать верхнюю границу, которая запускает GC и максимум, который может быть выделен? Как только выделенная память попадает в верхнюю границу, GC может вбить и освободить некоторую память в свободный пул. вроде как я убираю свой стол, который я разделяю с моим коллегой. У меня большой стол, и мой порог того, сколько мусора я могу терпеть на столе, намного меньше, чем размер моего стола. Мне не нужно заполнять каждый доступный дюйм до сбора мусора.

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

Ответ 7

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

Память без кучи включает в себя библиотеки DLL или SO, которые включают JVM и любой собственный код, который используется, а также скомпилированный Java-код, стеки потоков, собственные объекты, PermGen (метаданные о скомпилированных классах), среди других применений. Я видел сбои в работе программ Java, потому что столько памяти было отдано куче, что в приложении закончилась память без кучи. Здесь я узнал, что может быть важно зарезервировать память для использования без кучи, не устанавливая слишком большую кучу.

Это значительно увеличивает разницу в 32-битном мире, где приложение часто имеет только 2 ГБ виртуального адресного пространства, чем в 64-битном мире, конечно.

Ответ 8

Он выделяет память по мере необходимости, до -Xmx;)

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

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

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

JVM также нуждается в непрерывном блоке системной памяти, чтобы сбор мусора выполнялся эффективно.

ИЗМЕНИТЬ

Прилагаемая ошибка памяти или здесь

JVM, по-видимому, позволит сохранить некоторую память, но она редкая с конфигурацией по умолчанию.

Ответ 9

Просто одно из многих мест, где Java действительно хорош для серверов и действительно плохо для настольных компьютеров. Удивительно, что вы можете дать ему максимальный и минимальный объем памяти на сервере, JVM потеряет много мощности, если он только увеличится, пока не начнет пейджинг на диск. Однако это действительно ужасно для настольного приложения, работающего на разных машинах:)