Сколько памяти занимают объекты Java, которые вы пишете?

Java

memory

Обзор

Являясь объектно-ориентированным языком, Java предоставляет нам такие возможности, как полиморфизм, наследование, инкапсуляция и т. д., так что мы можем использовать эти возможности для простого создания кода, который легко расширять и поддерживать. какJavaer, и заниматься "объектами" каждый день, так сколько памяти занимает объект, который вы пишете? Посмотрим, как "потеряется" ваш "объект".

Среда этой статьи: jdk1.8_64

Модель памяти заголовка объекта Java

Чтобы понять, сколько памяти занимает объект Java, вы должны сначала понять одинJavaЧто такое модель памяти объектов? Поскольку наши виртуальные машины делятся на 32-битные и 64-битные, они должны иметь разные модели.Ниже я перечисляю 32-битные виртуальные машины и 64-битные виртуальные машины.JavaМодель памяти заголовка объекта.

32位虚拟机
32-битная виртуальная машина

64位虚拟机
64-битная виртуальная машина

64位带指针压缩
64-битный со сжатием указателя

Поскольку локальная среда автораjdk1.8,64-битная виртуальная машина, здесь я анализирую ее с 64-битной виртуальной машиной (с включенным сжатием указателя), потому что по умолчанию,jdk1.8Сжатие указателей включено по умолчанию на 64-разрядных виртуальных машинах.

Заголовок объекта Java в основном состоит из двух частей, первая частьMark Word, что такжеJavaВажная часть принципа реализации блокировки, другая частьKlass Word.

Klass WordНа самом деле это дизайн виртуальной машиныoop-klass modelмодель, здесьOOPОтносится кOrdinary Object Pointer(простой указатель объекта), то, что выглядит как указатель, на самом деле является объектом, скрытым внутри указателя. а такжеklassсодержит метаданные и информацию о методе для описанияJavaДобрый. Он занимает 32 бита пространства на 64-битной виртуальной машине с включенными сжатыми указателями.

Mark WordЭто центр нашего анализа, и здесь также будут разработаны соответствующие знания о замках.Mark WordЗанимает 64 бита пространства в среде 64-битной виртуальной машины. весьMark WordСуществует несколько случаев выделения:

  1. Разблокирован (обычный):хэш-код (identity_hashcode) занимает 31 бит, а возраст поколения (age) занимает 4 бита, режим смещения (biased_lock) занимает 1 бит, флаг блокировки (lock) занимает 2 бита, остальные 26 бит не используются (то есть все 0)
  2. Пристрастный:Идентификатор потока занимает 54 бита,epochЗанимает 2 бита, возраст поколения (age) занимает 4 бита, режим смещения (biased_lock) занимает 1 бит, флаг блокировки (lock) занимает 2 бита, а оставшийся 1 бит не используется.
  3. Легкий заблокированный: Указатель блокировки занимает 62 бита, метка блокировки (lock) занимает 2 бита.
  4. Тяжеловес закрыт: Указатель блокировки занимает 62 бита, метка блокировки (lock) занимает 2 бита.
  5. Флаг GC: Бит маркера занимает 2 бита, а остальные пустые (то есть заполнены 0)

Выше приведен наш анализ модели памяти заголовка объекта Java.Поскольку это объект Java, он обязательно будет включать заголовок объекта, а это означает, что эта часть использования памяти неизбежна.Поэтому в среде авторской 64-битной виртуальной машины Jdk1.8 (с включенным сжатием указателей) любой объект ничего не делает, пока объявлен класс, его занимаемая память не менее 96 бит, то есть при не менее 12 байт.

Проверить модель

Давайте напишем код для проверки приведенной выше модели памяти.Здесь рекомендуется Openjdk.джолтулс, который может помочь вам увидеть использование памяти объектом.

Сначала добавьте зависимости maven

        <dependency>
            <groupId>org.openjdk.jol</groupId>
            <artifactId>jol-core</artifactId>
            <version>0.10</version>
        </dependency>

Давайте сначала посмотрим: если мы просто создадим новый общий класс и не добавим никаких атрибутов, сколько места будет занято?

/**
 * @description:
 * @author: luozhou
 * @create: 2020-02-26 10:00
 **/
public class NullObject {

}

Согласно нашему предыдущему анализу модели объектной памяти Java, пустой объект, то есть имеется только один заголовок объекта, будет занимать 96 бит, или 12 байт, при условии сжатия указателя.

Запустите инструмент, чтобы просмотреть использование пространства

 public static void main(String[] args) {
        System.out.println(ClassLayout.parseInstance(new NullObject()).toPrintable());
    }

Приведенная выше строка кода анализирует, сколько памяти вы создаете для нового объекта NullObject. Давайте выполним и посмотрим, что получится в результате:

内存占用
использование памяти

Здесь мы видим, что результаты показывают:Instance size:16 bytes, результат равен 16 байтам, что не совпадает с 12 байтами, которые мы предсказывали ранее, почему так? Мы видим, что на изображении выше 3 строкиobject header, каждая занимает 4 байта, поэтому заголовок занимает 12 байт, что согласуется с нашим расчетом, а последняя строка — это 4 байта, заполненных виртуальной машиной,Так почему же виртуальная машина заполняется 4 байтами?

что такое выравнивание памяти

Чтобы понять, почему виртуальная машина дополняет 4 байта, нам нужно понять, что такое выравнивание памяти?

Наши программисты смотрят на память так:

На приведенном выше рисунке показан метод чтения памяти одной косточки и одной редьки. Но на самом деле ЦП не читает и не записывает память побайтно. Наоборот, CPU читает память поблочно, а размер блока может быть 2, 4, 6, 8, 16 байт и т.д. размер блока мы называем этогранулярность доступа к памяти. Как показано ниже:

Предполагая, что процессор 32-разрядной платформы, он будет считывать блоки памяти с шагом 4 байта. Итак, зачем вам нужно выравнивание памяти? Есть две основные причины:

  • Причина платформы (переносимости): не все аппаратные платформы могут получать доступ к произвольным данным по произвольным адресам. Например: конкретная аппаратная платформа позволяет получать только определенный тип данных по определенному адресу, в противном случае это вызовет исключение.
  • Причины производительности: доступ к невыровненной памяти приведет к тому, что ЦП выполнит два доступа к памяти и потратит дополнительные тактовые циклы на выравнивание и арифметику. Самой выровненной памяти требуется только один доступ для завершения операции чтения.

Я использую легенду, чтобы проиллюстрировать процесс доступа ЦП к не выровненным по памяти:

На приведенном выше рисунке предполагается, что ЦП считывает по 4 байта за раз в этом непрерывном 8-байтовом пространстве памяти, если мои данные не выровнены и сохраненный блок памяти находится по адресу 1, 2, 3, 4, тогда ЦП read потребуется два чтения, и есть дополнительные вычислительные операции:

  1. ЦП впервые считывает первый блок памяти по невыровненному адресу, читая 0-3 байта. и удалите ненужный байт 0.
  2. ЦП снова считывает второй блок памяти по невыровненному адресу, читая 4-7 байт. И удалить ненужные байты 5, 6, 7 байт.
  3. Объедините 1-4 байта данных.
  4. Поместить в реестр после слияния.

Таким образом, отсутствие выравнивания памяти приведет к тому, что ЦП будет выполнять дополнительные операции чтения и требовать дополнительных вычислений. Если выполняется выравнивание памяти, ЦП может напрямую читать с адреса 0, и нужные данные могут быть прочитаны за один раз, без дополнительных операций чтения и арифметических операций, что экономит время работы.Мы обмениваем пространство на время, поэтому нам необходимо выравнивание памяти.

Возвращаясь к пустому объекту Java, заполненному 4 байтами,Поскольку исходный заголовок байта составляет 12 байт, на 64-битной машине, если память выровнена, это 128 бит, то есть 16 байт, поэтому нам нужно дополнить 4 байта.

Непустые объекты занимают вычисление памяти

Мы знаем, что пустой объект занимает 16 байт, так сколько же байтов занимает непустой объект? Мы по-прежнему пишем общий класс для проверки:

public class TestNotNull {
    private NullObject nullObject=new NullObject();
    private int a;
}

Этот демонстрационный класс знакомит с другими объектами, которые мы знаем.intТип 4 байта,NullObjectОбъект занимает 16 байт, заголовок объекта занимает 12 байт,Есть еще одна очень важная ситуацияNullObjectЭто ссылка в текущем классе, поэтому реального объекта нет, а есть только адрес ссылки., адрес ссылки занимает 4 байта, поэтому всего 12+4+4=20 байт, а выравнивание памяти составляет 24 байта. Давайте проверим, является ли это результатом:

public static void main(String[] args) {
        //打印对象内存占用
        System.out.println(ClassLayout.parseInstance(new TestNotNull()).toPrintable());
        System.out.println("=========================");
        //输出对象相关所有内存占用
        System.out.println(GraphLayout.parseInstance(new TestNotNull()).toPrintable());
        System.out.println("=========================");
        //输出内存占用统计
        System.out.println(GraphLayout.parseInstance(new TestNotNull()).toFootprint());
    }

Результат выглядит следующим образом:

Мы видим, чтоTestNotNullЗанимаемое пространство класса составляет 24 байта, из них заголовок занимает 12 байт, переменнаяaдаintТип, занимает 4 байта, переменнаяnullObjectЭто эталон, заняла 4 байта, и, наконец, дополнив 4 байта в общей сложности 24 байта, что соответствует нашему предыдущему прогнозу.Однако, поскольку мы создаем экземплярNullObject, Этот объект будет существовать в памяти какое-то время, поэтому нам также нужно добавить 16 байт памяти для этого объекта, то есть всего 24 байта + 16 байт = 40 байт. Окончательный отпечаток статистики на нашем графике также составляет 40 байт, поэтому наш анализ верен.

Это также идея о том, как проанализировать, сколько памяти на самом деле занимает объект.Согласно этой идее и инструменту jol openJDK, вы можете в основном понять, сколько памяти написанный вами «объект» — пустая трата денег.

Суммировать

В этой статье я в основном описываю, как проанализировать, сколько места в памяти занимает объект Java.Основные выводы следующие:

  1. Модель памяти заголовков объектов Java отличается в 32-разрядных виртуальных машинах и 64-разрядных виртуальных машинах.64-разрядные виртуальные машины делятся на две модели заголовков объектов с включенным сжатием указателя и без сжатия указателя, поэтому всего существует 3 объекта. модели заголовков. .
  2. Выравнивание памяти в основном связано с причинами платформы и причинами производительности.Эта статья в основном анализирует причины производительности.
  3. При расчете занятости памяти пустого объекта необходимо вычислить выравнивание памяти.Для расчета памяти непустого объекта обратите внимание на добавление эталонной занятости памяти и занятости пространства исходного экземпляра объекта.

Ссылаться на

1.Adult.open JDK.java.net/~Lifo Forum/No…

2.gist.GitHub.com/Artur покраска корпуса двери…

3.еженедельный гик LY.GitHub.IO/articles/44…

4.developer.IBM.com/articles/afraid…