В этой
статье, я постараюсь кратко освятить из чего состоит память jvm, основные её части, дать краткий курс
молодого бойца, по памяти. И так в рабочей программе мы может получить
некоторые значения о памяти, и какой памятью мы располагаем, для этого нам
нужно обратиться к объекту Runtime, смотрим на листинг:
package org.vit.ch1;
public class Test {
public Test() {
System.out.printf("%s", "Hello JVM!\n");
int i = 1; int j = 0;
while (j < 4) {
i += 1;
if (i > 1000000) {
i = 0;
j++;
int mb1 = 0;
mb1 = 1024; //*
1024;
//Getting the
runtime reference from system
Runtime runtime =
Runtime.getRuntime();
//Print used
memory
System.out.println("Использованная Memory:"
+
(runtime.totalMemory() - runtime.freeMemory()) / mb1 + "Kb");
//Print free
memory
System.out.println("Свободная Memory:"
+
runtime.freeMemory() / mb1 + "Kb");
//Print total
available memory
System.out.println("Всего Memory:" +
runtime.totalMemory() / mb1 + "Kb");
//Print Maximum available memory
System.out.println("Max Memory которой мы обладаем:" +
runtime.maxMemory() / mb1 + "Kb");
}
}
}
public static void main(String
args[]) {
new Test();
}
}
Мы не
будем полагаться только на объект runtime, запустим
jvm с параметрами помеченными желтым цветом,
которые помогут нам понять, из каких частей состоит наша память, поможет нам в
этом механизм Native Memory Tracking (NMT) который
специально разработан для анализа физической памяти которую потребляет jvm:
C:\jboss-4.2.0.GA\vit\poi_excel\c1\target\classes>java
-Xms4m -Xmx64m -XX:+UnlockDiagnosticVMOptions
-XX:NativeMemoryTracking=detail -XX:+PrintNMTStatistics -cp "." org.vit.ch1.Test
Hello JVM!
Использованная Memory:717Kb
Свободная Memory:2866Kb
Всего Memory:3584Kb
Max Memory которой мы
обладаем:58368Kb
Native Memory Tracking:
Total: reserved=1409435KB, committed=48735KB
- Java Heap (reserved=65536KB, committed=4096KB)
(mmap:
reserved=65536KB, committed=4096KB)
- Class (reserved=1062062KB,
committed=10158KB)
(classes #567)
(malloc=5294KB
#228)
(mmap:
reserved=1056768KB, committed=4864KB)
- Thread (reserved=15423KB,
committed=15423KB)
(thread #16)
(stack: reserved=15360KB,
committed=15360KB)
(malloc=45KB #81)
(arena=18KB #30)
- Code (reserved=249659KB, committed=2595KB)
(malloc=59KB #358)
(mmap:
reserved=249600KB, committed=2536KB)
- GC (reserved=8331KB, committed=8039KB)
(malloc=5771KB
#118)
(mmap:
reserved=2560KB, committed=2268KB)
- Compiler (reserved=133KB, committed=133KB)
(malloc=2KB #32)
(arena=131KB #3)
- Internal (reserved=5398KB, committed=5398KB)
(malloc=5334KB
#1513)
(mmap:
reserved=64KB, committed=64KB)
- Symbol (reserved=1538KB, committed=1538KB)
(malloc=986KB #229)
(arena=552KB #1)
- Native Memory Tracking (reserved=122KB, committed=122KB)
(malloc=64KB #1016)
(tracking
overhead=57KB)
….
Как вы видите объект runtime, выдал нам немного информации, а NMT более подробную. Давайте рассмотрим все
выделенные желтым цветом кусочки памяти NMT, общее между всеми ними - это параметры reserved – зарезервированная память всего на которую мы можем рассчитывать,
committed – используемая, выделенная для
использования в текущий момент память. Total - всего памяти у jvm, т. е. сумма всех кусочков
памяти, итого что мы имеем. Java Heap
–
память jvm выделяемая во время запуска, где хранятся и
живут все создаваемые нами объекты, переменные, массивы и т. д. Обрабатывается
специальным менеджером памяти, автоматически – garbage collector (сборщиком мусора),
вы можете попросить его только выделить память, сборку мусора(освобождение
памяти) он делает автоматически, но обычно его настраивают и выбирают тип GC. Class - с 8 версии jvm, называется class metadata
или в более ранних версиях jvm - PermSize, там хранятся все метаданные
загруженные для вас ClassLoader, ещё его называют Method
Area. Thread – потоки по русски, я дальше буду
пользоваться где возможно английской нотацией так мне проще. Память, которая
используется thread, содержит
служебную информацию нужную для работы потока, для вызовов методов java кода. Каждый thread содержит program counter (PC) register указатель
на текущие инструкции, которые должны быть выполнены, можно сказать, что это
указатель на байт код, который выполняется в текущий момент. PC register существует, если вызываются не native (родные)
СИ методы. Если вы пишете “вставки” на языке СИ, то у
такого thread не существует pc register, у
него вызов кода идет из native method stack, т.
е. каждый thread имеет NMS,
если метод написан на СИ. Идем
дальше, каждый thread
содержит свой собственный стек для
вызова методов написанных на языке java
- jvm stack, в
стеке содержатся frames, области
памяти, которые в свою очередь содержат все нужные данные для выполнения метода: local variables (локальные
переменные), operand stack (стек
опкодов, опкоды – специальные команды jvm – вроде собственного языка assembler’a ), и другие данные которыми оперирует метод, такие как, ссылки
на объекты в java heap, обработка
исключений, хранение промежуточных данных вычислений, возвращаемые результаты,
эту третью часть стека называют
frame data. Code – память которая содержит сгенерированный
код jit компилятором,
еще его называют code
cache, смотритепредыдущую статью о компиляторах.
GC – память,
где содержатся вспомогательные структуры данных, которые используются сборщиком
мусора при сборке мусора. Compiler – память которая используется при генерации кода jit компилятором. Internal – память которая используется парсером
командной строчки, содержит свойства системы – properties, используется JVMTI, и другими мелкими, встроенными в jvm функциональными
утилитами, не заслуживающими нашего внимания, но требующими памяти. Symbol
- память выделенная специальной таблице ссылок на строки и символы.
Как мы знаем, все объекты String при
создании попадают в heap из них
могут встречаться одинаковые строки, вот эти строки могут быть оптимизированы
или попросту может не быть 2-х одинаковых строк, а может быть просто ссылки на
неё в таблице ссылок. Native Memory Tracking - понятно без слов, память используемая
самим NMT.
Ладно, с названиями разобрались, теперь попробуем представить
графически, главные куски памяти и рассмотреть примитивные настройки которые рекомендуют
размеры памяти. Для этого нам придётся
вспомнить предыдущую статью и использовать параметр -XX:+PrintFlagsFinal,
который выводит все настройки виртуальной машины java:
java
-Xms4m -Xmx64m -XX:+PrintFlagsFinal -cp "." org.vit.ch1.Test Давайте начнем с Java Heap, начальное значение, которое будет выделено памяти, при инициализации jvm устанавливается параметром –Xms, до параметра -Xmx может расти при необходимости. В нашем случае если рассмотреть - Java Heap (reserved=65536KB, committed=4096KB), то мы получим 65536 KB / 1024 = 64 Mb, параметр командной строчки -Xmx64m, 4096 KB / 1024 = 4 Mb, параметр -Xms4m, здесь все идеально. Method Area – в 8 версии jvm есть два параметра которые задают начальный при инициализации размер -XX:MetaspaceSize и конечный размер -XX:MaxMetaspaceSize до которого может расти наш method area, теоритически это верно, но если посмотреть, что нам выдает NMT Class (reserved=1062062KB, committed=10158KB), то вы увидите не то что ожидали. committed=10158KB – это реальная память которая выделена для загрузки 567 классов - (classes #567), т.е. параметры -XX:MetaspaceSize, -XX:MaxMetaspaceSize – это рекомендуемые параметры, а не параметры к использованию как в Java Heap. Code Cache – дела обстоят как с method area, начальный рекомендуемый, приблизительный размер задается параметром -XX:InitialCodeCacheSize, и конечный приблизительный -XX:ReservedCodeCacheSize. У каждого потока (thead) можно задать размер стека параметром –Xss, т.е. теоритически количество памяти каждого потока равна, размеру стека + размер PC register, разглядеть её можно в строчке (stack: reserved=15360KB, committed=15360KB), по умолчанию размер стека равен одному мегабайту. Так же у нас есть память, которая называется Direct Memory. Количество выделяемой памяти задается параметром -XX:MaxDirectMemorySize, выделяется, когда вы пишете на СИ и вызываете метод malloc(), или когда пользуетесь методом allocateDirect()при выделении буферов памяти в NIO. Вот пока все о памяти jvm.