Java: почему он использует фиксированный объем памяти? или как он управляет памятью? - PullRequest
12 голосов
/ 16 августа 2010

Кажется, что 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 здесь. GC отвечает только за то, какие объекты могут быть удалены, и за инициализацию освобождения памяти, но за фактическоеосвобождение - это другая подсистема. Afaik, это некая реализация пула памяти, а не просто вызов free.)


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

Ответы [ 7 ]

15 голосов
/ 17 августа 2010

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

Знание того, где есть свободная памятьделает эффективное выделение новых объектов молодому поколению эффективным и предотвращает необходимость бегать туда и обратно к базовой ОС.JIT Masamitsu из 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 , но есть много хороших альтернатив на выбор.

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

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

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

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

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

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

7 голосов
/ 16 августа 2010

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

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

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

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

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

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

3 голосов
/ 16 августа 2010

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

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, даже если в ОС достаточно ОЗУ.

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


Edit:

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

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

2 голосов
/ 17 августа 2010

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

2 голосов
/ 16 августа 2010

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

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

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

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

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

2 голосов
/ 16 августа 2010

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

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

0 голосов
/ 16 августа 2010

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

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

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

Подробнее об этой сложной теме см. Настройка сборки мусора

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...