А релизе
8u131 решена проблем с текущей памятью JVM.
Проблема наблюдается в релизах младше 131
http://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8164293
вторник, 27 июня 2017 г.
среда, 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:Полный список "NMT Memory Categories" с их описанием можно посмотреть тут.
- 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)
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:Под heap было выделено пространство [0x00000000e0000000 - 0x0000000100000000] reserved 524288KB
[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
В него включено
[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 означает, что память пока только заразервирована.
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Помимо размера выделенной commited и reserved memory можно обнаружить вызов выделения области сжатых указателей Metaspace::allocate_metaspace_compressed_klass_ptrs, который резервирует в область размером в гигабайт.
[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
В документации такое поведение объясняется необходимостью содержать отдельную структуру в памяти, обеспечивающую хранение смещения 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
Ограничить область памяти Calss можно ключами -XX:MaxMetaspcaseSize и XX:CompressedClassSpaceSize
[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*,
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Можно заметить, что размер стэка 1028KB, а не 1024КБ.
[0x00007f7adc64f884] JavaThread::run()+0x24
[0x00007f7adc500568] java_start(Thread*)+0x108
вывод 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
(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)
(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
четверг, 15 июня 2017 г.
JAVA memory usage. Часть 1.
В этом посте будет рассмотриваться процесс JVM и в общих чертах описан механизм использования им памяти.
В ОС Linux процессы не работают с памятью напрямую. Каждому процессу выделяется виртуальное пространство, к которому он обращается при необходимости. В момент непосредственного использования памяти ядро системы ассоциирует адрес в виртуальном адресном пространстве процесса со страницами памяти, которыми располагает система (физически существующей памятью). Процесс обеспечивается механизмом lazy allocation.
lazy allocation, в первом приближении, - механизм выделения памяти, подразумевающий, что пока страница памяти, выделенная в адресном пространстве пользовательского процесса, не будет запрошена, она не ассоциируется с физически существующей памятью.
Из принципа работы этого механизма: процессу/процессам ОС может быть выделено больше "виртуальной" памяти, чем физически располагает система. Подобная ситуация не всегда допустима, поэтому процессу, запрашивающему себе память в своём виртуальном адресном пространстве иногда требуется явно указать, как она будет соотносится с физической памятью системы. В связи с этим для дальнейшего понимания вывода диагностических утилит, необходимо ввести классификацию памяти.
Reserved memory - память, которая может быть будет выделена процессу. У любого процесса Reserved memory может быть больше сумы физического объёма памяти в системе.
Выделяется в виртуальном адресном пространстве без выделения физической памяти. Выделяется методом mmap с указанием ключа PROT_NONE. Т.к. страницы выделены в витр. адресном пространстве, они резервируют часть адресов этого адресного пространства и другие вызовы mmap не будут пытаться использовать уже занятые адреса, но обращния к этим страницам будет вызывать ошибку с отправкой процессу сигнала SIGSEGV.
Commited memory - память в виртуальном адресном пространстве, выделенная процессу. От reserved отличается тем, что резервируется не с флагом PROT_NONE, а, например, с PROT_READ | PROT_WRITE. Страницы commited memory, в отличие от reserved memory, не только зарезервированы в виртуальном адресном пространстве процесса, но и могут быть замаплены на физическую память системы без получения процессом SIGSEGV. При обращении к таким страницам ядро обрабатывает этот вызов, привязывая страницы памяти виртуального адресного пространства процесса и физически существующей памяти прозрачно для процесса.
Resident memory - память, выделенная процессу, в которую процесс записал что-либо.
lazy allocation, в первом приближении, - механизм выделения памяти, подразумевающий, что пока страница памяти, выделенная в адресном пространстве пользовательского процесса, не будет запрошена, она не ассоциируется с физически существующей памятью.
Из принципа работы этого механизма: процессу/процессам ОС может быть выделено больше "виртуальной" памяти, чем физически располагает система. Подобная ситуация не всегда допустима, поэтому процессу, запрашивающему себе память в своём виртуальном адресном пространстве иногда требуется явно указать, как она будет соотносится с физической памятью системы. В связи с этим для дальнейшего понимания вывода диагностических утилит, необходимо ввести классификацию памяти.
Reserved memory - память, которая может быть будет выделена процессу. У любого процесса Reserved memory может быть больше сумы физического объёма памяти в системе.
Выделяется в виртуальном адресном пространстве без выделения физической памяти. Выделяется методом mmap с указанием ключа PROT_NONE. Т.к. страницы выделены в витр. адресном пространстве, они резервируют часть адресов этого адресного пространства и другие вызовы mmap не будут пытаться использовать уже занятые адреса, но обращния к этим страницам будет вызывать ошибку с отправкой процессу сигнала SIGSEGV.
Commited memory - память в виртуальном адресном пространстве, выделенная процессу. От reserved отличается тем, что резервируется не с флагом PROT_NONE, а, например, с PROT_READ | PROT_WRITE. Страницы commited memory, в отличие от reserved memory, не только зарезервированы в виртуальном адресном пространстве процесса, но и могут быть замаплены на физическую память системы без получения процессом SIGSEGV. При обращении к таким страницам ядро обрабатывает этот вызов, привязывая страницы памяти виртуального адресного пространства процесса и физически существующей памяти прозрачно для процесса.
Resident memory - память, выделенная процессу, в которую процесс записал что-либо.
Как это соотносится с памятью (heap) процесса JVM
Resident memory
Для примера можно рассмотреть процессjava -jar /opt/gcviewer/gcviewer-1.35-SNAPSHOT.jarвывод top по этому процессу
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
7284 user 20 0 5299360 210658 29432 S 0.3 1.6 0:03.74 java
Столбец RES означает, сколько памяти в данный момент используется процессом. В моём случае используется 210658 килобат. Это resident memory или по-другому RSS (Resident Set Size).
На графике использования heap из jvisualvm синяя область - это часть resident memory процесса. То есть та память, которая заполнена чем-либо.
Использующаяся часть heap - это, конечно, не вся resident memory рассматриваемого процесса. Вся resident memory процесса будет складываться из занятых Heap и Native областей.
Commited memory.
На том же графике можно увидеть, что текущий Heap size состовляет около 150 Мб.Текуций heap size JVM - это commited memory.
Текущий размер heap - это не вся commited memory рассматриваемого процесса, а её часть.
Общий объём commited memory процесса складывается из commited memory heap и native областей памяти процесса.
Reserved memory
Если запустить тот же процесс, задав размеры heap ключами -Xms512m -Xmx100G, что заведомо больше, чем есть на моём сервере,java -Xms512m -Xmx100G, -jar /opt/gcviewer/gcviewer-1.35-SNAPSHOT.jarвывод top покажет, что занимаемая процессом Resident memory практически не изменилась и составлет 219560 Кбайт
Tasks: 1 total, 0 running, 1 sleeping, 0 stopped, 0 zombieНа графике jvisualvm
%Cpu(s): 2.7 us, 1.1 sy, 0.0 ni, 94.6 id, 0.9 wa, 0.4 hi, 0.3 si, 0.0 st
KiB Mem : 8075464 total, 1616492 free, 3249608 used, 3209364 buff/cache
KiB Swap: 3145724 total, 3145724 free, 0 used. 4035756 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
8398 user 20 0 0.103t 219560 29464 S 0.3 2.7 0:03.79 java
Размер использующейся в Heap памяти (Resident memory) остался почти таким же, как и при предыдущем запуске - около 20 Mb.
Размер выделенной под heap Commited memory сразу после запуска составляет 512Mб. С точки зрения JVM ключ -Xms указывает, какой размер Heap должен быть выделен при старте JVM. С точки зрения OC, виртуальное адресноге пространство памяти процесса JVM, равное 512m, должно быть зарезервировано в памяти ОС.
JVM смогла запуститься без ошибок, несмотря на то, что заданная верхняя граница памяти Heap превышает количество памяти на сервере. То есть в виртуальном адресном пространстве памяти процесса было зарезервировано 100Gb, но пока эта память не используется, она никак не привязяна к физической памяти. -Xmx100G - это Reserved memory. Когда Heap (в моём примере он равен 512M) заполнится до такой степени, что JVM потребуется его увеличение, JVM запросит у ОС зарезервированную память (reserved memory).
В моём примере во время старта JVM под процесс выделяется 512mb (commited memory) и резервируется рамять в 100Gb (reserved memory). После увеличения размера heap (не графике справа) размер commited memory стал около 600 mb
Подписаться на:
Сообщения (Atom)