Перейти к содержанию

Как получить размер объекта в Java

В отличие от C/C++, где можно использовать метод sizeof() для получения размера объекта в байтах, в Java нет настоящего эквивалента такого метода.

В этой статье покажем, как можно получить размер конкретного объекта.

Потребление памяти в Java

Хотя в Java нет оператора sizeof, он нам и не нужен. Все типы-примитивы имеют стандартный размер и обычно не содержат байтов заполнения или выравнивания. Тем не менее, это не всегда просто.

Хотя примитивы должны вести себя так, как будто они имеют официальные размеры, JVM может хранить данные любым способом, который ей нравится, с любым количеством заполнения или накладных расходов. Она может хранить boolean[] в 64-битных long фрагментах, таких как BitSet, размещать некоторые временные объекты в стеке или оптимизировать некоторые переменные или вызовы методов, полностью исключая их существование, заменяя их константами и т. д. Но пока программа дает тот же результат, это прекрасно.

Принимая также во внимание влияние оборудования и кешей ОС (данные могут дублироваться на каждом уровне кеша), можно только приблизительно предсказать потребление оперативной памяти.

Объекты, ссылки и классы-оболочки

Минимальный размер объекта составляет 16 байтов для современного 64-разрядного JDK, поскольку объект имеет 12-байтовый заголовок, дополненный до числа, кратного 8 байтам. В 32-разрядном JDK служебные данные составляют 8 байтов, дополненных до числа, кратного 4 байтам.

Ссылки имеют типичный размер 4 байта на 32-битных платформах и на 64-битных платформах с границей кучи менее 32 Гб (-Xmx32G) и 8 байт для этой границы выше 32 Гб.

Это означает, что 64-битная JVM обычно требует на 30-50% больше места в куче.

Особенно уместно отметить, что упакованные типы, массивы, строки и другие контейнеры, такие как многомерные массивы, требуют больших затрат памяти, поскольку они добавляют определенные накладные расходы. Например, когда сравниваем примитив int (который занимает всего 4 байта) с объектом Integer, который занимает 16 байтов, видим, что накладные расходы памяти составляют 300%.

Оценка размера объекта с помощью инструментов

Один из способов получить оценку размера объекта в Java – использовать метод getObjectSize(Object) интерфейса Instrumentation, представленный в Java 5.

Как видим в документации Javadoc, метод обеспечивает «зависимую от реализации аппроксимацию» указанного размера объекта. Следует отметить, что существует потенциальное включение служебных данных в размер, и значения могут быть разными во время одного вызова JVM.

Этот подход поддерживает только оценку размера самого рассматриваемого объекта, а не размеров объектов, на которые он ссылается. Чтобы оценить общий размер объекта, понадобится код, который просматривает эти ссылки и вычисляет предполагаемый размер.

Создание Instrumentation Agent

Чтобы вызвать Instrumentation.getObjectSize(Object) для получения размера объекта, нужно сначала получить доступ к экземпляру Instrumentation. Нужно использовать instrumentation agent. Есть два способа сделать это, как описано в документации к пакету java.lang.instrument.

Instrumentation agent можно указать через командную строку или можно использовать его с уже работающей JVM. Сосредоточимся на первом.

Чтобы указать instrumentation agent через командную строку, потребуется реализация перегруженного метода premain, который будет сначала вызываться JVM при использовании instrumentation. Кроме того, нужно предоставить статический метод, чтобы получить доступ к Instrumentation.getObjectSize(Object).

Теперь создадим класс InstrumentationAgent:

public class InstrumentationAgent {
    private static volatile Instrumentation globalInstrumentation;

    public static void premain(final String agentArgs, final Instrumentation inst) {
        globalInstrumentation = inst;
    }

    public static long getObjectSize(final Object object) {
        if (globalInstrumentation == null) {
            throw new IllegalStateException("Agent not initialized.");
        }
        return globalInstrumentation.getObjectSize(object);
    }
}

Прежде чем создадим JAR для этого агента, нужно убедиться, что в него включен простой метафайл MANIFEST.MF:

Premain-class: com.baeldung.objectsize.InstrumentationAgent

Теперь можно создать JAR-файл агента с включенным файлом MANIFEST.MF. Один из способов – через командную строку:

javac InstrumentationAgent.java
jar cmf MANIFEST.MF InstrumentationAgent.jar InstrumentationAgent.class

Пример класса

Посмотрим на это в действии, создав класс с образцами объектов, которые будут использовать класс агента:

public class InstrumentationExample {

    public static void printObjectSize(Object object) {
        System.out.println("Object type: " + object.getClass() +
          ", size: " + InstrumentationAgent.getObjectSize(object) + " bytes");
    }

    public static void main(String[] arguments) {
        String emptyString = "";
        String string = "Estimating Object Size Using Instrumentation";
        String[] stringArray = { emptyString, string, "com.baeldung" };
        String[] anotherStringArray = new String[100];
        List<String> stringList = new ArrayList<>();
        StringBuilder stringBuilder = new StringBuilder(100);
        int maxIntPrimitive = Integer.MAX_VALUE;
        int minIntPrimitive = Integer.MIN_VALUE;
        Integer maxInteger = Integer.MAX_VALUE;
        Integer minInteger = Integer.MIN_VALUE;
        long zeroLong = 0L;
        double zeroDouble = 0.0;
        boolean falseBoolean = false;
        Object object = new Object();

        class EmptyClass {
        }
        EmptyClass emptyClass = new EmptyClass();

        class StringClass {
            public String s;
        }
        StringClass stringClass = new StringClass();

        printObjectSize(emptyString);
        printObjectSize(string);
        printObjectSize(stringArray);
        printObjectSize(anotherStringArray);
        printObjectSize(stringList);
        printObjectSize(stringBuilder);
        printObjectSize(maxIntPrimitive);
        printObjectSize(minIntPrimitive);
        printObjectSize(maxInteger);
        printObjectSize(minInteger);
        printObjectSize(zeroLong);
        printObjectSize(zeroDouble);
        printObjectSize(falseBoolean);
        printObjectSize(Day.TUESDAY);
        printObjectSize(object);
        printObjectSize(emptyClass);
        printObjectSize(stringClass);
    }

    public enum Day {
        MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
    }
}

Чтобы это работало, нужно включить параметр –javaagent с путем к JAR-файлу агента при запуске приложения:

VM Options: -javaagent:"path_to_agent_directory\InstrumentationAgent.jar"

Результат запуска класса покажет предполагаемые размеры объектов:

Object type: class java.lang.String, size: 24 bytes
Object type: class java.lang.String, size: 24 bytes
Object type: class [Ljava.lang.String;, size: 32 bytes
Object type: class [Ljava.lang.String;, size: 416 bytes
Object type: class java.util.ArrayList, size: 24 bytes
Object type: class java.lang.StringBuilder, size: 24 bytes
Object type: class java.lang.Integer, size: 16 bytes
Object type: class java.lang.Integer, size: 16 bytes
Object type: class java.lang.Integer, size: 16 bytes
Object type: class java.lang.Integer, size: 16 bytes
Object type: class java.lang.Long, size: 24 bytes
Object type: class java.lang.Double, size: 24 bytes
Object type: class java.lang.Boolean, size: 16 bytes
Object type: class com.baeldung.objectsize.InstrumentationExample$Day, size: 24 bytes
Object type: class java.lang.Object, size: 16 bytes
Object type: class com.baeldung.objectsize.InstrumentationExample$1EmptyClass, size: 16 bytes
Object type: class com.baeldung.objectsize.InstrumentationExample$1StringClass, size: 16 bytes

Заключение

В этой статье описано, как память используется определенными типами в Java, как JVM хранит данные, и выделены факторы, которые могут повлиять на общее потребление памяти. Затем продемонстрировано, как на практике можно получить приблизительный размер объектов Java.

Полный код, относящийся к этой статье, можно найти на GitHub.