Как узнать размер объекта в Java

1. Обзор
В отличие от C/C++, где мы можем использовать метод sizeof() для получения размера объекта в байтах, в Java нет эквивалента такому методу.
В этой статье мы рассмотрим способ получения размера конкретного объекта.
2. Потребление памяти в Java
Хотя в Java и нет метода для получения размера объекта, мы в нем и не нуждаемся. Все примитивные типы имеют стандартный размер и, как правило, какое-либо выравнивание данных отсутствует. Тем не менее, это не всегда просто.
Хотя примитивы должны вести себя так, как если бы они имели конкретные размеры, JVM может хранить данные любым удобным способом, с любым размером и накладными расходами. Она может хранить boolean[ ] в 64-битных длинных кусках, таких как BitSet, сохранять временные объекты в стек или оптимизировать некоторые переменные или вызовы методов, заменяя их константами и т. д. Все эти оптимизации делаются при условии, что программа будет выдавать тот же результат, что и раньше и это прекрасно.
Учитывая влияние аппаратного обеспечения и операционной системы на наши программы (данные которых могут дублироваться на каждом уровне кэша), мы можем только грубо прогнозировать потребление оперативной памяти.
2.1 Объекты, ссылки и классы-обертки
Минимальный размер объекта для современного 64-битного JDK составляет 16 байт, так как объект имеет 12-байтовый заголовок, дополненный до размера, кратного 8-ми байтам. В 32-битной JDK, накладные расходы составляют 8 байт, дополненные до кратности 4-ем.
Ссылки имеют стандартный размер в 4 байта для 32-битной и 64-битной платформы с размером кучи менее 32 Гб (-Xmx32G) и 8 байт для кучи свыше 32 Гб. Это значит, что 64-битной JVM требуется размер кучи на 30−50% больше, чем 32-х битной.
Особенно важно отметить, что классы-обертки, массивы, строки, многомерные массивы являются дорогостоящими, поскольку они добавляют определенные расходы. Например, если мы сравним примитивный тип int (занимает 4 байта) с его оберткой Integer (занимает 16 байт), то накладные расходы составят 300%.
3. Оценка размера объекта с помощью Java Instrumentation
Одним из способов получить размер объекта в Java является использование метода getObjectSize (Object) интерфейса Instrumentation, введенного в Java 5.
Как мы могли увидеть в документации Java, этот метод предоставляет приближенное значение размера объекта. Стоить отметить, что существует вероятность включения накладных расходов в размер. Значение размера конкретного объекта может изменяться во время работы JVM.
Этот подход позволяет произвести только оценку рассматриваемого объекта, но не размеры объектов на которые он ссылается. Чтобы рассчитать полный размер объекта, нам необходим код, который пробежится по всем ссылкам и рассчитает примерный размер.
3.1 Создание Instrumentation Agent
Чтобы вызвать Instrumentation.getObjectSize (Object), для получения размера объекта, мы должны иметь возможность сначала получить доступ к экземпляру Instrumentation. Для этого нам нужно использовать Instrumentation Agent, и есть два способа сделать это, как описано в документации для пакета java.lang.instrument:
  1. Instrumentation Agent может быть указан через командную строку
  2. мы можем использовать его с уже запущенной JVM
Давайте сосредоточимся на первом способе.
Чтобы указать Instrumentation Agent через командную строку, нам понадобится реализация перегруженного метода premain, который будет первым вызываться JVM при использовании инструментария. Кроме того, нам нужно предоставить статический метод для доступа к 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
3.2 Пример класса
Давайте рассмотрим на практике, создав класс с примерами объектов, которые будут использовать наш класс:
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"
Результат работы нашего класса покажет нам приблизительные размеры объектов:
4. Вывод
В этой статье мы описали, как память используется конкретными типами в Java, как JVM хранит данные и может повлиять на общее потребление памяти. Затем мы продемонстрировали, как можно на практике получить приблизительные размеры Java-объектов
Оригинал статьи «How to Get the Size of an Object in Java»
Оцените статью, если она вам понравилась!