- PVSM.RU - https://www.pvsm.ru -
(пост из серии «будни перформанс-инженеров»)
Привет! Наблюдая достаточно много постов про занимаемое объектами пространство в Java, решил написать пост, срывающий покровы. Для понимания происходящего неплохо было бы ориентироваться в азах устройства Java heap'а, минимальных размерах базовых типов [1], продвинутых штук, типа сжатых указателей [2].
Миф 0. Можно раз и навсегда узнать, сколько будет занимать объект в памяти.
Реальность: Зависит как минимум от: а) целевой JVM, будь то HotSpot, JRockit, J9 или ещё что-нибудь; б) битности, как минимум размеры указателей могут различаться, а то и базовые типы могут быть представлены другими размерами (при поддержке семантики языка), в) включённых и случившихся оптимизаций, типа инлайна объектов, скаляризаций, паддингов, г) и ещё тучи всяких штук, по сравнению с которыми фазы Луны куда более предсказуемы.
Миф 1. Для подсчёта размера объекта достаточно сложить размеры полей. Всем известно, сколько занимает поле конкретного примитивного типа, а уж тем более, сколько занимает ссылка.
Реальность: про размеры базовых типов см. Миф 0. Кроме всего прочего конкретные платформы могут требовать выравнивания полей из соображений корректности (некоторые процы вообще не работают с misaligned данными), либо из соображений производительности. Далее, во многих случаях объекты тоже должны быть выровнены, и поэтому за каждым инстансом будет следовать паддинг. Кроме того, есть ещё заголовок объекта…
Миф 2. Для подсчёта размера объекта достаточно сложить размеры полей и погуглить размеры заголовков объектов в распространённых реализациях JVM.
Реальность: у этой информации достаточно маленький «shelf life», т.е. она довольно резво устаревает. Например, в JRockit'е заголовки могут быть 8 байт, а в HotSpot'е — до 16. Но это только пока мы не доберёмся переделать схему блокировок, и в HS не появятся такие же сжатые заголовки. И тогда половина интернетов будет бить себя пяткой в грудь про 16-байтовые заголовки, а вторая — про 8-байтовые.
Миф 3. Любой нормальный инструмент покажет реальный размер объекта.
Реальность: ха-ха, зависит от определения нормальности. «Нормальный» HotSpot'овский HPROF, например, будет считать размеры объекта так, как описано в мифе 1. По этому поводу все тулы, вроде jhat, MAT, и прочих post-mortem тулов обречены на пере/недооценку размера объектов.
Мораль: Нам нужны online-тулы, которые бы давали информацию о размере объектов прямо на месте.
Возмём для примера обычный такой HashMap, на обычном таком Linux x86_64. Инстанциируем один HashMap, сделаем хипдамп и посмотрим на этот хипдамп разными инструментами. Вот такие размеры HashMap'а в байтах нам рапортуют эти инструменты:
JVM | VisualVM | Eclipse MAT |
---|---|---|
HotSpot (7u10) x86 | 45 | 48 |
HotSpot (7u10) x86_64 | 69 | 72 |
HotSpot (7u10) x86_64 (compressed refs) | 69 | 56 |
JRockit (6u37, R28.2.5) x86 | 48 | 48 |
JRockit (6u37, R28.2.5) x86_64 | 76 | 80 |
JRockit (6u37, R28.2.5) x86_64 (compressed refs) | 76 | 56 |
Как видно, тулы друг с другом во многом не соглашаются, и на то есть причина: в формате HPROF'а отсутствует информация о расположении полей, поэтому в конце концов приходится гадать о размерах заголовков, размерах полей и заниматься прочей магией.
Однако, у нас есть магия более высокого уровня, когда мы можем напрямую у VM спросить о структуре объектов, но для этого нам придётся пользоваться Unsafe (дьявольский смех), diagnostic MXBeans (дьявольский смех) и делать всё это в онлайне (дьявольский смех). Всё это счастье уже объединено в мой плюшевый проектик на GitHub: https://github.com/shipilev/java-object-layout [3]
Он точно работает для HotSpot и JRockit; может приемлемо работать и для других VM (буду признателен, если кто-нибудь дофиксит это до J9, SAP и прочих, мне по некоторым причинам юридического толка этим заниматься нельзя).
Посмотрим на всё тот же HashMap при помощи нашего волшебного тула:
HotSpot x86:
Running 32-bit HotSpot VM.
Objects are 8 bytes aligned.
java.util.HashMap
offset size type description
0 8 (assumed to be the object header + first field alignment)
8 4 Set AbstractMap.keySet
12 4 Collection AbstractMap.values
16 4 int HashMap.size
20 4 int HashMap.threshold
24 4 float HashMap.loadFactor
28 4 int HashMap.modCount
32 4 int HashMap.hashSeed
36 1 boolean HashMap.useAltHashing
37 3 (alignment/padding gap)
40 4 Entry[] HashMap.table
44 4 Set HashMap.entrySet
48 (object boundary, size estimate)
VM reports 48 bytes per instance
Что мы видим в этом дампе?
HotSpot x86_64:
Running 64-bit HotSpot VM.
Objects are 8 bytes aligned.
java.util.HashMap
offset size type description
0 16 (assumed to be the object header + first field alignment)
16 8 Set AbstractMap.keySet
24 8 Collection AbstractMap.values
32 4 int HashMap.size
36 4 int HashMap.threshold
40 4 float HashMap.loadFactor
44 4 int HashMap.modCount
48 4 int HashMap.hashSeed
52 1 boolean HashMap.useAltHashing
53 3 (alignment/padding gap)
56 8 Entry[] HashMap.table
64 8 Set HashMap.entrySet
72 (object boundary, size estimate)
VM reports 72 bytes per instance
Поанализируем:
HotSpot x86_64 (compressed references):
Running 64-bit HotSpot VM.
Using compressed references with 3-bit shift.
Objects are 8 bytes aligned.
java.util.HashMap
offset size type description
0 12 (assumed to be the object header + first field alignment)
12 4 Set AbstractMap.keySet
16 4 Collection AbstractMap.values
20 4 int HashMap.size
24 4 int HashMap.threshold
28 4 float HashMap.loadFactor
32 4 int HashMap.modCount
36 4 int HashMap.hashSeed
40 1 boolean HashMap.useAltHashing
41 3 (alignment/padding gap)
44 4 Entry[] HashMap.table
48 4 Set HashMap.entrySet
52 4 (loss due to the next object alignment)
56 (object boundary, size estimate)
VM reports 56 bytes per instance
Поанализируем:
JRockit x86:
Running 32-bit JRockit VM.
Objects are 8 bytes aligned.
java.util.HashMap
offset size type description
0 8 (assumed to be the object header + first field alignment)
8 4 Set AbstractMap.keySet
12 4 Collection AbstractMap.values
16 4 Entry[] HashMap.table
20 4 Object[] HashMap.cache
24 4 Set HashMap.entrySet
28 4 int HashMap.cache_bitmask
32 4 int HashMap.size
36 4 int HashMap.threshold
40 4 float HashMap.loadFactor
44 4 int HashMap.modCount
48 (object boundary, size estimate)
VM reports 48 bytes per instance
Поанализируем:
JRockit x86_64:
Running 64-bit JRockit VM.
Objects are 8 bytes aligned.
java.util.HashMap
offset size type description
0 8 (assumed to be the object header + first field alignment)
8 8 Set AbstractMap.keySet
16 8 Collection AbstractMap.values
24 8 Entry[] HashMap.table
32 8 Object[] HashMap.cache
40 8 Set HashMap.entrySet
48 4 int HashMap.cache_bitmask
52 4 int HashMap.size
56 4 int HashMap.threshold
60 4 float HashMap.loadFactor
64 4 int HashMap.modCount
68 4 (loss due to the next object alignment)
72 (object boundary, size estimate)
VM reports 72 bytes per instance
Поанализируем:
JRockit x86-64, compressed refs:
Running 64-bit JRockit VM.
Using compressed references with 0-bit shift.
Objects are 8 bytes aligned.
java.util.HashMap
offset size type description
0 8 (assumed to be the object header + first field alignment)
8 4 Set AbstractMap.keySet
12 4 Collection AbstractMap.values
16 4 Entry[] HashMap.table
20 4 Object[] HashMap.cache
24 4 Set HashMap.entrySet
28 4 int HashMap.cache_bitmask
32 4 int HashMap.size
36 4 int HashMap.threshold
40 4 float HashMap.loadFactor
44 4 int HashMap.modCount
48 (object boundary, size estimate)
VM reports 48 bytes per instance
Анализ:
Таким образом, можно посмотреть на реальные размеры объектов в полной табличке:
JVM | VisualVM | Eclipse MAT | java-object-layout |
---|---|---|---|
HotSpot (7u10) x86 | 45 (врёт) | 48 | 48 |
HotSpot (7u10) x86_64 | 69 (врёт) | 72 | 72 |
HotSpot (7u10) x86_64 (compressed refs) | 69 (врёт) | 56 | 56 |
JRockit (6u37, R28.2.5) x86 | 48 | 48 | 48 |
JRockit (6u37, R28.2.5) x86_64 | 76 (врёт) | 80 (врёт) | 72 |
JRockit (6u37, R28.2.5) x86_64 (compressed refs) | 76 (врёт) | 56 (врёт) | 48 |
Как видно, только в тривиальных случаях у нас всё хорошо, но чуть в сторону — враньё.
У волшебного HS есть волшебная опция -XX:ObjectAlignmentInBytes, которая управляет выравниванием объектов (кроме всего прочего, она позволяет использовать сжатые указатели на огромных хипах). Если задать нашему тесту 128-байтовое выравнивание, то мы получим:
Running 64-bit HotSpot VM.
Using compressed references with 7-bit shift.
Objects are 128 bytes aligned.
java.util.HashMap
offset size type description
0 12 (assumed to be the object header + first field alignment)
12 4 Set AbstractMap.keySet
16 4 Collection AbstractMap.values
20 4 int HashMap.size
24 4 int HashMap.threshold
28 4 float HashMap.loadFactor
32 4 int HashMap.modCount
36 4 int HashMap.hashSeed
40 1 boolean HashMap.useAltHashing
41 3 (alignment/padding gap)
44 4 Entry[] HashMap.table
48 4 Set HashMap.entrySet
52 76 (loss due to the next object alignment)
128 (object boundary, size estimate)
VM reports 128 bytes per instance
… в то время как VisualVM радостно отрапортует всё те же 69 байт. Eclipse MAT отрапортует все 128 байт, видимо, пронюхав, что все объекты в хипе выровнены на 128. Но его мы тоже сломаем, когда у нас появится @Contended [4], и тогда такой безобидный классик:
public static class Test2 {
@Contended private int int1;
private int int2;
}
… отрапортуется java-object-layout как:
Running 64-bit HotSpot VM.
Using compressed references with 3-bit shift.
Objects are 8 bytes aligned.
Test8003985.Test2
offset size type description
0 12 (assumed to be the object header + first field alignment)
12 4 int Test2.int2
16 128 (alignment/padding gap)
144 4 int Test2.int1
148 128 (alignment/padding gap)
276 4 (loss due to the next object alignment)
280 (object boundary, size estimate)
VM reports 280 bytes per instance
… в то время как, VisualVM и Eclipse MAT отрапортуют примерно по 24 байта, что, конечно, ЛПИП!
Автор: TheShade
Источник [6]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/java/24101
Ссылки в тексте:
[1] базовых типов: http://docs.oracle.com/javase/specs/jls/se7/html/jls-4.html#jls-4.2
[2] сжатых указателей: https://blogs.oracle.com/jrockit/entry/understanding_compressed_refer
[3] https://github.com/shipilev/java-object-layout: https://github.com/shipilev/java-object-layout
[4] @Contended: http://openjdk.java.net/jeps/142
[5] бесполезные переговоры: http://mail.openjdk.java.net/pipermail/serviceability-dev/2012-December/007844.html
[6] Источник: http://habrahabr.ru/post/164777/
Нажмите здесь для печати.