Задавались ли вы когда-нибудь вопросом, как выглядят java объекты изнутри?
Под катом будет подробное описание заголовка java объекта, из чего он состоит и сколько занимает памяти.
Для начала вспомним, что в jvm любой объект в памяти состоит из заголовка объекта и переменных объекта (ссылки и примитивы). Также финальный размер объекта может быть расширен, чтобы размер стал кратен 8 байтам.
Заголовок каждого объекта (кроме массива) состоит из двух машинных слов — mark word и сlass word. Массивы имеют дополнительно 32 бита для описания длины массива.
В Mark word хранится identity hashcode, биты используемые сборщиком мусора и биты используемые для блокировок. Более подробно можно всегда ознакомиться в соответствующих сорцах OpenJDK markOop.hpp.
В Class word хранится указатель на сам класс, то есть, на то место, где лежит информация о данном типе данных: методы, аннотации, наследование и другое. Более подробно можно также всегда ознакомиться в соответствующих сорцах OpenJDK klass.hpp.
Давайте теперь более подробно рассмотрим заголовок объекта и в частности mark word
32 битные jvm
Как видно из таблицы, содержание Mark word может сильно разниться в зависимости от текущего состояния объекта.
Нормальное состояние объекта (biased_lock = 0, lock = 01)
- identity_hashcode — хеш объекта, который появляется лениво. Если у объекта будет впервые вызов System.identityHashCode(obj), то этот хеш будет рассчитан и записан в заголовок объекта.
В других состояниях, когда за объект конкурируют различные потоки, identity_hashcode будет храниться уже не в заголовке объекта, а в мониторе объекта. - age — количество пережитых сборок мусора. Когда age достигает числа max-tenuring-threshold,
объект перемещается в область хипа old generation. - biased_lock — содержит 1, если biased locking включено для этого объекта, иначе 0.
Когда Biased Locking включено, объект как бы смещается к первому объекту, захватившему его монитор. Последующий захват объекта тем же потоком будет немного быстрее.
Вот основные теоретические предпосылки для данной блокировки:
- На протяжении всей жизни объекта им преимущественно владеет один поток
- Если поток недавно использовал блокировку на этом объекте, скорей всего кеш процессора, будет еще содержать данные которые будут нужны для повторного захвата этого объекта.
Biased Locking по умолчанию включено начиная с java 6, -XX:-UseBiasedLocking
- lock — содержит код состояние блокировки. 00 — Lightweight Locked, 01 — Unlocked or Biased, 10 — Heavyweight Locked, 11 — Marked for Garbage Collection.
То есть в таблице состояние объекта определяют совокупность битов biased_lock и lock.
Режим Biased Locked (biased_lock = 1, lock = 01)
- thread — в режиме biased блокировки предполагается, что объектом преимущественно владеет какой-то конкретный поток, в поле хранится id этого потока.
- epoch содержит некоторый временной индикатор владения объектом потоком, id которого сохранено в thread
Режим Lightweight Locked (lock = 00)
В этом режиме предполагается, что время захвата данным объектом разными потоками не пересекается вообще или пересекается незначительно. В этом режиме вместо тяжеловесных блокировок операционной системы, JVM использует атомики.
- ptr_to_lock_record — для установки/ожидания блокировки используется CAS (compare and set) внутри цикла spin loop.
Для справки, минимальное время блокировки ОС будет в районе порядка 10 мс, при помощи атомиков поток не засыпает, а продолжает молотить небольшой цикл, и как только ресурс освободится, цикл с атомиком закончится, и поток тут же захватит этот объект.
Режим Heavyweight Locked (lock = 10)
- ptr_to_heavyweight_monitor — если время захвата данным объектом различными потоками будет значительно пересекаться, то lightweight lock будет заменена на heavyweight lock. В ptr_to_heavyweight_monitor будет записан указатель на монитор. Используется блокировка ОС.
Итак в 32 битной jvm хедер объекта состоит из 8 байт. Массивы дополнительно имеют 4 байта.
64 битные jvm
На 64 битной jvm хедер объекта состоит из 16 байт. Массивы дополнительно имеют 4 байта.
64 битные jvm с сжатием указателей
Хедер объекта состоит из 12 байт. Массивы дополнительно имеют 4 байта.
Немного о сжатии указателей. Для 32 битного указателя адресное пространство ограничено 4Гб. Однако если снова вспомнить, что в jvm размер объекта кратен 8 байтам, то мы можем использовать псевдо 35 битный указатель, с тремя нулями на конце. И, таким образом, ссылаться уже на 32Гб памяти. Сжатие получается не бесплатное, цена — дополнительная операция (pointer << 3) при любом обращении в heap.
Ссылка на оригинальную статью:
Также хотел бы добавить, что всё здесь описанное не является догмой, возможно, в других версиях jvm заголовок объекта будет отличаться. Описанное тут актуально для openjdk 8.
easty
Тут ещё можно уточнить про сжатие указателей, если jvm "сожрёт" больше 32гб, все ссылки автоматически переконвертятся и сразу jvm увеличит свой хип в 1.5 раза. Важный момент в тюнинге jvm