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

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

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

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

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

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

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


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

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

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


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

Ответ 1

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

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

Распределение Fast-path не вызывает в 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 GUI работают с большой памятью, но у меня нет никаких доказательств так или иначе. У вас есть конкретный пример, на который мы могли бы смотреть?

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

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

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

Ответ 2

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

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

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

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

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

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

Ответ 3

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

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 немедленно спросит ОС о 1,024 МБ оперативной памяти, и это будет показано в диспетчере задач Windows. В работоспособности этот ram не используется, но 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 имеют разные модели распределения RAM, но JVM может абстрагировать это и следить за тем же использованием памяти для разных ОС.

Ответ 4

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

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

Ответ 5

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

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

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

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

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

Ответ 6

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

Ответ 7

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

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

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

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