среда, 21 июня 2017 г.

JAVA memory usage. Часть 2.

Native Memory Tracking.

С версии 1.8u40 в Oracle JDK включена возможность получать информацию о текущем использовании NativeMemory - Native Memory Tracking.


Для включения NMT необходимо добавить ключ  -XX:NativeMemoryTracking=detail
java -Xms450m -Xmx512m -XX:NativeMemoryTracking=detail -jar /opt/gcviewer/gcviewer-1.35-SNAPSHOT.jar
Для просмотра общей ниформации используется утилита  jcmd с ключём summary
/opt/oracle/java/latest/bin/jcmd <pid> VM.native_memory summary
Native Memory Tracking:


-                 Java Heap (reserved=524288KB, committed=460800KB)
                            (mmap: reserved=524288KB, committed=460800KB)

-                     Class (reserved=1072564KB, committed=26548KB)
                            (classes #3395)
                            (malloc=5556KB #2675)
                            (mmap: reserved=1067008KB, committed=20992KB)

-                    Thread (reserved=20648KB, committed=20648KB)
                            (thread #21)
                            (stack: reserved=20560KB, committed=20560KB)
                            (malloc=64KB #106)
                            (arena=23KB #40)

-                      Code (reserved=250784KB, committed=8012KB)
                            (malloc=1184KB #2669)
                            (mmap: reserved=249600KB, committed=6828KB)

-                        GC (reserved=24933KB, committed=24729KB)
                            (malloc=5773KB #176)
                            (mmap: reserved=19160KB, committed=18956KB)

-                  Compiler (reserved=150KB, committed=150KB)
                            (malloc=20KB #110)
                            (arena=131KB #3)

-                  Internal (reserved=5853KB, committed=5853KB)
                            (malloc=5821KB #4999)
                            (mmap: reserved=32KB, committed=32KB)

-                    Symbol (reserved=4901KB, committed=4901KB)
                            (malloc=2432KB #2557)
                            (arena=2469KB #1)

-    Native Memory Tracking (reserved=362KB, committed=362KB)
                            (malloc=121KB #1921)
                            (tracking overhead=240KB)

-               Arena Chunk (reserved=187KB, committed=187KB)
                            (malloc=187KB)

Полный список "NMT Memory Categories" с их описанием можно посмотреть тут.

1. Java Heap

Область памяти в которой создаются объекты приложения.
-                 Java Heap (reserved=524288KB, committed=460800KB)
                            (mmap: reserved=524288KB, committed=460800KB)

На примере, рассмотренном в предыдущем посте, описано, что ключ Xmx указывает количество памяти, выделяемое в виртуальном адресном пространстве процесса. Эта память никак не мапится на физическую память сервера.
-Xmx512m - reserved memory  (reserved=524288KB)
ключ Xms указывает, какое количество памяти должно быть выделено (mmaped), но не обязательно использовано для heap-области процесса JVM на момент его старта. Эта память отображаться как Commited Memory. Если Xms меньше Xmx, то количество commited memory может увеличиваться по мере необходимости до порога, заданного значением reserved memory.
В примере -Xms450m - commited memory (committed=460800KB).

более подробную статистику по процессу можно посмотреть с помощью
/opt/oracle/java/latest/bin/jcmd <pid> VM.native_memory detail
Статитстика, относящая к Heap представлена сразу после суммарной статистики
Virtual memory map:
[0x00000000e0000000 - 0x0000000100000000] reserved 524288KB for Java Heap from
    [0x00007f9986b73552] ReservedSpace::initialize(unsigned long, unsigned long, bool, char*, unsigned long, bool)+0xc2
    [0x00007f9986b73f2e] ReservedHeapSpace::ReservedHeapSpace(unsigned long, unsigned long, bool, char*)+0x6e
    [0x00007f9986b40f2b] Universe::reserve_heap(unsigned long, unsigned long)+0x8b
    [0x00007f99869f7474] ParallelScavengeHeap::initialize()+0x84
        [0x00000000e0000000 - 0x00000000f2c00000] committed 307200KB from
            [0x00007f9986a431e3] PSVirtualSpace::expand_by(unsigned long)+0x53
            [0x00007f9986a332f7] PSOldGen::initialize(ReservedSpace, unsigned long, char const*, int)+0xb7
            [0x00007f9986387cea] AdjoiningGenerations::AdjoiningGenerations(ReservedSpace, GenerationSizer*, unsigned long)+0x39a
            [0x00007f99869f75c6] ParallelScavengeHeap::initialize()+0x1d6
        [0x00000000f5580000 - 0x00000000feb80000] committed 153600KB from
            [0x00007f9986a431e3] PSVirtualSpace::expand_by(unsigned long)+0x53
            [0x00007f9986a441a5] PSYoungGen::initialize_virtual_space(ReservedSpace, unsigned long)+0x75
            [0x00007f9986a44b0e] PSYoungGen::initialize(ReservedSpace, unsigned long)+0x3e
            [0x00007f9986387c95] AdjoiningGenerations::AdjoiningGenerations(ReservedSpace, GenerationSizer*, unsigned long)+0x345
Под heap было выделено пространство  [0x00000000e0000000 - 0x0000000100000000] reserved 524288KB
В него включено
[0x00000000e0000000 - 0x00000000f2c00000] committed 307200KB - под Old Gen
[0x00000000f5580000 - 0x00000000feb80000] committed 153600KB - под  Young Gen

С помощью команды
pmap -X <pid>
пожно получить аналогичные данные

java -Xms450m -Xmx512m -XX:NativeMemoryTracking=detail -jar /opt/gcviewer/gcviewer-1.35-SNAPSHOT.jar
         Address Perm   Offset Device    Inode    Size    Rss    Pss Referenced Anonymous ShmemPmdMapped Shared_Hugetlb Private_Hugetlb Swap SwapPss Locked Mapping
        00400000 r-xp 00000000  00:26   315842       4      4      2          4         0              0              0               0    0       0      0 java
        00600000 rw-p 00000000  00:26   315842       4      4      4          4         4              0              0               0    0       0      0 java
        021eb000 rw-p 00000000  00:00        0     132     20     20         20        20              0              0               0    0       0      0 [heap]
        e0000000 rw-p 00000000  00:00        0  307200     12     12         12        12              0              0               0    0       0      0
        f2c00000 ---p 00000000  00:00        0   42496      0      0          0         0              0              0               0    0       0      0
        f5580000 rw-p 00000000  00:00        0  153600 113832 113832     113832    113832              0              0               0    0       0      0
        feb80000 ---p 00000000  00:00        0   20992      0      0          0         0              0              0               0    0       0      0
       100000000 rw-p 00000000  00:00        0    2816   2768   2768       2768      2768              0              0               0    0       0      0
Диапазон адресов, отданных под heap выделен красным.
Текущие области памяти New и Old выделены синим. Атрибуты rw-p в столбце Per:
rw - возможна запись и чтение памяти (PROT_READ | PROT_WRITE аргументы были заданы при выделении этой области памяти)
p - память выделена как private
сочетание разрешение ---p означает, что память пока только заразервирована.

JVM non Heap (Native) memory.


Все остальные области памяти процесса JVM относятся к так называемой non-Heap memory. Эти области памяти выделяются в Native memory ОС. Максимальное количество Native памяти, которую может использовать JVM процесс не ограничено по умолчанию.

2. Class

Эта область памяти используется для хранения метаданных классов. Данная область памяти использует Native память ОС. Верхняя граница количества памяти, использующейся под class metadata задаётся ключём -XX:MaxMetaspaceSize.

-                     Class (reserved=1072564KB, committed=26548KB)
                            (classes #3395)
                            (malloc=5556KB #2675)
                            (mmap: reserved=1067008KB, committed=20992KB)

Вывод утилиты показывает, что Reserved память составляет 1 Gb, но она практически не используется. Такой объём reserved memory объясняется nем, что для JVM с размером Heap меньше 32 Гб используется дефолтное значение ключа -XX:+UseCompressedOops. В свою очередь, при использовании этого ключа дефолтное значение ключа -XX:CompressedClassSpaceSize=1G.

1072564KB только резервируется процессом и не обязательно будет использоваться. В выводе видно, что размер committed memory всего 26548KB
Более подробный вывод:
/opt/oracle/java/latest/bin/jcmd <pid> VM.native_memory detail

[0x0000000100000000 - 0x0000000140000000] reserved 1048576KB for Class from
    [0x00007fed5e22c552] ReservedSpace::initialize(unsigned long, unsigned long, bool, char*, unsigned long, bool)+0xc2
    [0x00007fed5e22c78b] ReservedSpace::ReservedSpace(unsigned long, unsigned long, bool, char*, unsigned long)+0x1b
    [0x00007fed5e00d080] Metaspace::allocate_metaspace_compressed_klass_ptrs(char*, unsigned char*)+0x40
    [0x00007fed5e00f55f] Metaspace::global_initialize()+0x4cf
        [0x0000000100000000 - 0x00000001002c0000] committed 2816KB from
            [0x00007fed5e22bfc9] VirtualSpace::expand_by(unsigned long, bool)+0x199
            [0x00007fed5e00b4e6] VirtualSpaceList::expand_node_by(VirtualSpaceNode*, unsigned long, unsigned long)+0x76
            [0x00007fed5e00e300] VirtualSpaceList::expand_by(unsigned long, unsigned long)+0xf0
            [0x00007fed5e00e493] VirtualSpaceList::get_new_chunk(unsigned long, unsigned long, unsigned long)+0xb3

Помимо размера выделенной commited и reserved memory можно обнаружить вызов выделения области сжатых указателей  Metaspace::allocate_metaspace_compressed_klass_ptrs, который резервирует в область размером в гигабайт.
В документации такое поведение объясняется необходимостью содержать отдельную структуру в памяти, обеспечивающую хранение смещения 64 битных указателей классов для работы с 32 битными указателями JVM. Наличие этой области актуально только при значениях ключей -XX:+UseCompressedOops и -XX:+UseCompressedClassesPointers использующихся по умолчанию для Heap меньше 32Gb.
Размер области сжатых указателей звдаётся ключём -XX:CompressedClassSpaceSize
При его чрезмерном уменьшениии возможна ошибка java.lang.OutOfMemoryError: Compressed class space

При отключении использования сжатых указателей (не рекомендуется т.к. даёт большой overhead на использование памяти) ключём -XX:-UseCompressedOops  вывод NMT будет следующим:

java -Xms450m -Xmx512m -XX:-UseCompressedOops -XX:NativeMemoryTracking=detail -jar /opt/gcviewer/gcviewer-1.35-SNAPSHOT.jar

[0x00007fe6f9000000 - 0x00007fe708000000] reserved 245760KB for Code from
    [0x00007fe70f3036e6] ReservedSpace::initialize(unsigned long, unsigned long, bool, char*, unsigned long, bool)+0x256
    [0x00007fe70f303d4c] ReservedCodeSpace::ReservedCodeSpace(unsigned long, unsigned long, bool)+0x2c
    [0x00007fe70ee5ae85] CodeHeap::reserve(unsigned long, unsigned long, unsigned long)+0xa5
    [0x00007fe70ecbfeb0] CodeCache::initialize()+0x80
        [0x00007fe6f9570000 - 0x00007fe6f95a0000] committed 192KB from
            [0x00007fe70f302fc9] VirtualSpace::expand_by(unsigned long, bool)+0x199
            [0x00007fe70ee5ac6c] CodeHeap::expand_by(unsigned long)+0x8c
            [0x00007fe70ecc0200] CodeCache::allocate(int, bool)+0x60
            [0x00007fe70f12c13c] nmethod::new_nmethod(methodHandle, int, int, CodeOffsets*, int, DebugInformationRecorder*, Dependencies*, CodeBuffer*, int, OopMapSet*, 
Ограничить область памяти Calss  можно ключами -XX:MaxMetaspcaseSize и XX:CompressedClassSpaceSize

 3. Thread

Количество памяти, занимаемое стэками потоков. Размер стэка каждого потока задаётся ключём -Xss. Дефолтный размер стэка 1024 байт. Его можно посмотреть так:
/opt/oracle/java/latest/bin/java -XX:+PrintFlagsFinal -version | grep ThreadStackSize

intx VMThreadStackSize = 1024 {pd product}
На каждый поток выделяется стэк размером VMThreadStackSize.
В выводе NMT каждый стэк выглядит так:
[0x00007f7a90f84000 - 0x00007f7a91085000] reserved and committed 1028KB for Thread Stack from
    [0x00007f7adc64f884] JavaThread::run()+0x24
    [0x00007f7adc500568] java_start(Thread*)+0x108
Можно заметить, что размер стэка 1028KB, а не 1024КБ.

вывод pmap -X, соответствующий данному потоку показывает, что под него выделено 2 области памяти 12Кб и 1016 Кб.
Address Perm   Offset Device   Inode    Size    Rss    Pss Referenced Anonymous ShmemPmdMapped Shared_Hugetlb Private_Hugetlb Swap SwapPss Locked Mapping
    7f7a47be2000 ---p 00000000  00:00       0      12      0      0          0         0              0              0               0    0       0      0
    7f7a47be5000 rw-p 00000000  00:00       0    1016     88     88         88        88              0              0               0    0       0      0

Суммарная статистика показывает, что все стэки занимают 20648KB, что соответствует (20648-64-23)/1028=20 потокам.
-                    Thread (reserved=20648KB, committed=20648KB)
                            (thread #21)
                            (stack: reserved=20560KB, committed=20560KB)
                            (malloc=64KB #106)
                            (arena=23KB #40)

4. Code

В процессе работы процесс JVM выполняет некоторый байт-код. JVM может решить, что какой-то кусок кода выполняется достаточно часто и скомпилировать его в native-код. Native-код выполняется значительно быстрее и требует меньше ресурсов чем byte code. Для хранения скомпилированного кода используется native code cache. Размеры этой области задаются ключами
-XX:ReservedCodeCacheSize - максимальное значение
-XX:InitialCodeCacheSize - размер области, выделяемый при старте.
Подбор ReservedCodeCacheSize значения происходит индивидуально для каждого приложения.

При компиляуии код проходит несколько степеней оптимизации.
level 0 - Интерпретатор JVM.
level 1 - C1 с оптимизацией, без профилирования
level 2 - C1 с учётом счётчиков количества обращений и "backedge"
level 3 - C1 с оптимизацией и профилированием
level 4 - C2
Для отключения компиляции в Native код используется ключ -Xint (не рекомендуется)
Для отключения промежуточных фаз компилирования и хранения только кода С2 можно использвать ключ -XX:-TieredCompilation. При этом критерием компиляции станнет значение CompileThreshold.
java -XX:+PrintFlagsFinal -version | grep CompileThreshold
     intx CompileThreshold                          = 10000                               {pd product}
     intx Tier2CompileThreshold                     = 0                                   {product}
     intx Tier3CompileThreshold                     = 2000                                {product}
     intx Tier4CompileThreshold                     = 15000                               {product}


GC

Последняя относительно крупная область Native памяти JVM - память сборщика мусора. Она пропорциональна размеру Heap.
Если JDK1.8 использует сборщик мусора G1, статистика использования памяти этим сборщиком будет примерно такая:

для Xms = 450M область GC составляет около 24Mb
-                        GC (reserved=24933KB, committed=24729KB)
                            (malloc=5773KB #176)
                            (mmap: reserved=19160KB, committed=18956KB)
для Xms = 2G область GC составляет около 80Mb
-                        GC (reserved=82395KB, committed=82395KB)
                            (malloc=5771KB #121)
                            (mmap: reserved=76624KB, committed=76624KB)
для Xms = 4G область GC составляет около 160Mb

-                        GC (reserved=159015KB, committed=159015KB)
                            (malloc=5771KB #121)
                            (mmap: reserved=153244KB, committed=153244KB)


То есть примерно 40 Mb на гигабайт Heap

Для CMS сборщика следует учитывать не только область GC, но и область Unлnown т.к. Native Memory Tracker не умеет (и, наверное, уже никогда не научится) правильно идентифицировать области памяти, использующиеся этим сборщиком.

java +UseConcMarkSweepGC -XX:NativeMemoryTracking=detail -jar /opt/gcviewer/gcviewer-1.35-SNAPSHOT.jar

для Xms = 2G область GC составляет примерно 16+60 = 76Mb

-                        GC (reserved=16891KB, committed=16891KB)
                            (malloc=9359KB #144)
                            (mmap: reserved=7532KB, committed=7532KB)
-                   Unknown (reserved=60648KB, committed=60648KB)
                           (mmap: reserved=60648KB, committed=60648KB)
для Xms = 4G область GC составляет примерно 25+93 = 120Mb

-                        GC (reserved=25083KB, committed=25083KB)
                            (malloc=9359KB #144)
                            (mmap: reserved=15724KB, committed=15724KB)

-                   Unknown (reserved=93928KB, committed=93928KB)
                            (mmap: reserved=93928KB, committed=93928KB)

Особенности окружения

Для glibc 2.12 и 2.17 существует особенность выделения памяти областями по 64 Mb. Данные версии библиотеки используется в Red Hat Enterprise Linux 6 и Red Hat Enterprise Linux 7.  Я использую Fedora release 25 (Twenty Five) c версией пакета glibc-2.24-6.fc25.x86_64, где подобного поведения не наблюдается. При попытках повторить мои действия на ОС RHEL6/7 результаты теоретически должны сильно отличаться от результатов в Fedora 25. Несколько сократить фрагментацию должны помочь ключи:

-XX:CodeCacheExpansionSize=1M -XX:CodeCacheMinimumFreeSpace=1M
-XX:MinMetaspaceExpansion=1M -XX:MaxMetaspaceExpansion=8M




Комментариев нет:

Отправить комментарий