10.21.2015

Java HotSpot JVM Memory



В этой статье, я постараюсь кратко освятить из чего состоит память 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 – вроде собственного языка assemblera ),  и другие  данные которыми оперирует метод, такие как, ссылки на объекты в 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.