Перейти к основному содержимому

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

· 6 мин. чтения

1. Обзор

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

3.1. Создание агента инструментирования

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

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

Чтобы указать агент инструментирования через командную строку , нам потребуется реализация перегруженного метода 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.foreach.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.foreach" };
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.foreach.objectsize.InstrumentationExample$Day, size: 24 bytes
Object type: class java.lang.Object, size: 16 bytes
Object type: class com.foreach.objectsize.InstrumentationExample$1EmptyClass, size: 16 bytes
Object type: class com.foreach.objectsize.InstrumentationExample$1StringClass, size: 16 bytes

4. Заключение

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

Как всегда, полный код, относящийся к этой статье, можно найти в проекте GitHub .