Это была давняя жалоба на Java, но она в значительной степени бессмысленна и обычно основана на поиске неверной информации. Обычная формулировка выглядит примерно так: «Hello World на Java занимает 10 мегабайт! Зачем это нужно?» Что ж, вот способ заставить Hello World на 64-битной JVM претендовать на 4 гигабайта ... хотя бы одним способом измерения.
java -Xms1024m -Xmx4096m com.example.Hello
Различные способы измерения памяти
В Linux команда top выдает несколько разных чисел для памяти. Вот что говорит пример Hello World:
PID USER PR NI VIRT RES SHR S% CPU% MEM TIME + КОМАНДА
2120 кгрег. 20 0 4373 м 15 м 7152 S 0 0,2 0: 00,10 Ява
- VIRT - это пространство виртуальной памяти: сумма всего на карте виртуальной памяти (см. Ниже). Это в значительной степени бессмысленно, кроме случаев, когда это не так (см. Ниже).
- RES - это размер резидентного набора: количество страниц, которые в настоящее время находятся в оперативной памяти. Почти во всех случаях это единственное число, которое вы должны использовать, когда говорите «слишком большой». Но это все еще не очень хороший показатель, особенно если говорить о Java.
- SHR - это объем резидентной памяти, который используется совместно с другими процессами. Для процесса Java это обычно ограничивается общими библиотеками и отображенными в память JAR-файлами. В этом примере у меня был запущен только один процесс Java, поэтому я подозреваю, что 7k - это результат использования библиотек ОС.
- SWAP не включен по умолчанию и здесь не отображается. Он указывает объем виртуальной памяти, которая в настоящее время находится на диске, независимо от того , находится ли она на самом деле в области подкачки . Операционная система очень хорошо хранит активные страницы в оперативной памяти, и единственные способы ее замены - (1) купить больше памяти или (2) сократить количество процессов, поэтому лучше игнорировать это число.
Ситуация для диспетчера задач Windows немного сложнее. В Windows XP есть столбцы «Использование памяти» и «Размер виртуальной памяти», но в официальной документации ничего не говорится о том, что они означают. Windows Vista и Windows 7 добавляют больше столбцов, и они фактически задокументированы . Из них измерение «Рабочий набор» является наиболее полезным; это примерно соответствует сумме RES и SHR в Linux.
Понимание карты виртуальной памяти
Виртуальная память, используемая процессом, представляет собой сумму всего, что находится в карте памяти процесса. Это включает в себя данные (например, кучу Java), а также все общие библиотеки и файлы отображения памяти, используемые программой. В Linux вы можете использовать команду pmap, чтобы увидеть все объекты, отображенные в пространстве процесса (с этого момента я буду ссылаться только на Linux, потому что это то, что я использую; я уверен, что есть эквивалентные инструменты для Windows). Вот выдержка из карты памяти программы «Hello World»; вся карта памяти имеет длину более 100 строк, и нет ничего необычного в том, чтобы иметь список из тысячи строк.
0000000040000000 36K rx-- /usr/local/java/jdk-1.6-x64/bin/java
0000000040108000 8K rwx-- /usr/local/java/jdk-1.6-x64/bin/java
0000000040eba000 676K rwx-- [anon]
00000006fae00000 21248K rwx-- [anon]
00000006fc2c0000 62720K rwx-- [anon]
0000000700000000 699072K rwx-- [anon]
000000072aab0000 2097152K RWX-- [anon]
00000007aaab0000 349504K rwx-- [anon]
00000007c0000000 1048576K rwx-- [anon]
...
00007fa1ed00d000 1652K r-xs- /usr/local/java/jdk-1.6-x64/jre/lib/rt.jar
...
00007fa1ed1d3000 1024K rwx-- [anon]
00007fa1ed2d3000 4K ----- [anon]
00007fa1ed2d4000 1024K rwx-- [anon]
00007fa1ed3d4000 4K ----- [anon]
...
00007fa1f20d3000 164K rx-- /usr/local/java/jdk-1.6-x64/jre/lib/amd64/libjava.so
00007fa1f20fc000 1020K ----- /usr/local/java/jdk-1.6-x64/jre/lib/amd64/libjava.so
00007fa1f21fb000 28K rwx-- /usr/local/java/jdk-1.6-x64/jre/lib/amd64/libjava.so
...
00007fa1f34aa000 1576K rx-- /lib/x86_64-linux-gnu/libc-2.13.so
00007fa1f3634000 2044K ----- /lib/x86_64-linux-gnu/libc-2.13.so
00007fa1f3833000 16K rx-- /lib/x86_64-linux-gnu/libc-2.13.so
00007fa1f3837000 4K rwx-- /lib/x86_64-linux-gnu/libc-2.13.so
...
Краткое объяснение формата: каждая строка начинается с адреса виртуальной памяти сегмента. Далее следуют размер сегмента, разрешения и источник сегмента. Этот последний элемент является либо файлом, либо «anon», который указывает блок памяти, выделенный через mmap .
Начиная сверху, мы имеем
- Загрузчик JVM (т.е. программа, которая запускается при вводе
java
). Это очень мало; все, что он делает, это загружает в разделяемые библиотеки, где хранится настоящий код JVM.
- Связка аноновых блоков, содержащих кучу Java и внутренние данные. Это Sun JVM, поэтому куча разбита на несколько поколений, каждое из которых является собственным блоком памяти. Обратите внимание, что JVM выделяет пространство виртуальной памяти на основе
-Xmx
значения; это позволяет ему иметь непрерывную кучу. Это -Xms
значение используется внутри, чтобы сказать, сколько кучи «используется» при запуске программы, и запустить сборку мусора при приближении к этому пределу.
- Отображаемый в память JAR-файл, в данном случае файл, содержащий «классы JDK». Когда вы отображаете JAR в память, вы можете очень эффективно обращаться к файлам в нем (вместо того, чтобы каждый раз читать его с самого начала). Sun JVM отобразит в памяти все файлы JAR на пути к классам; если вашему приложению необходим код для доступа к JAR, вы также можете отобразить его в памяти.
- Данные по потокам для двух потоков. Блок 1M - это стек потоков. У меня не было хорошего объяснения для блока 4k, но @ericsoe идентифицировал его как «защитный блок»: у него нет разрешений на чтение / запись, поэтому при обращении к нему будет возникать ошибка сегмента, и JVM отлавливает это и переводит это к
StackOverFlowError
. Для реального приложения вы увидите десятки, если не сотни этих записей, повторенных через карту памяти.
- Одна из разделяемых библиотек, которая содержит реальный код JVM. Есть несколько из них.
- Общая библиотека для стандартной библиотеки C. Это только одна из многих вещей, которые загружает JVM, которые не являются строго частью Java.
Совместно используемые библиотеки особенно интересны: каждая разделяемая библиотека имеет как минимум два сегмента: сегмент только для чтения, содержащий код библиотеки, и сегмент чтения-записи, содержащий глобальные данные о процессах для библиотеки (я не знаю, что сегмент без разрешений есть; я видел его только на x64 Linux). Часть библиотеки, доступная только для чтения, может использоваться всеми процессами, которые используют библиотеку; например, libc
имеет 1,5 МБ виртуальной памяти, которую можно использовать совместно.
Когда важен размер виртуальной памяти?
Карта виртуальной памяти содержит много вещей. Некоторые из них доступны только для чтения, некоторые из них являются общими, а некоторые выделяются, но никогда не затрагиваются (например, почти все 4 Гб кучи в этом примере). Но операционная система достаточно умна, чтобы загружать только то, что ей нужно, поэтому размер виртуальной памяти в значительной степени не имеет значения.
Размер виртуальной памяти важен, если вы работаете в 32-битной операционной системе, где вы можете выделить только 2 ГБ (или, в некоторых случаях, 3 ГБ) адресного пространства процесса. В этом случае вы имеете дело с дефицитным ресурсом, и вам, возможно, придется пойти на компромисс, например, уменьшить размер кучи, чтобы отобразить в памяти большой файл или создать много потоков.
Но, учитывая, что 64-битные машины распространены повсеместно, я не думаю, что пройдет много времени, прежде чем объем виртуальной памяти станет абсолютно неактуальной статистикой.
Когда важен размер резидентного набора?
Размер резидентного набора - это та часть виртуальной памяти, которая фактически находится в ОЗУ. Если ваш RSS становится значительной частью вашей общей физической памяти, возможно, пришло время начать беспокоиться. Если ваш RSS-канал начинает занимать всю вашу физическую память, а ваша система начинает обмениваться, уже давно пора начать беспокоиться.
Но RSS также вводит в заблуждение, особенно на слегка загруженной машине. Операционная система не тратит много сил на восстановление страниц, используемых процессом. Это дает мало пользы и может привести к дорогостоящему отказу страницы, если процесс коснется страницы в будущем. В результате статистика RSS может включать в себя множество страниц, которые не используются активно.
Нижняя граница
Если вы не поменялись местами, не слишком переживайте о том, что говорит вам различная статистика памяти. С оговоркой, что постоянно растущая RSS может указывать на какую-то утечку памяти.
С Java-программой гораздо важнее обратить внимание на то, что происходит в куче. Важное значение имеет общий объем потребляемого пространства, и вы можете предпринять некоторые шаги для его уменьшения. Более важным является количество времени, которое вы тратите на сборку мусора, и какие части кучи собираются.
Доступ к диску (т. Е. К базе данных) стоит дорого, а память - дешево. Если вы можете обменять одно на другое, сделайте это.