Как стать автором
Обновить

Размеры Java-объектов разного типа

Время на прочтение6 мин
Количество просмотров7.1K

Введение


Содержит ли Java-объект:

  • поля, объявленные в суперклассе?
  • private поля, объявленные в суперклассе?
  • методы?
  • элементы массива?
  • длину массива?
  • другой объект (в себе)?
  • hash-код?
  • тип (свой)?
  • имя (своё)?

Ответы на эти (и другие) вопросы можно получить с помощью библиотеки классов org.openjdk.jol которая, в частности, позволяет уяснить, что объект — это область памяти:

  • содержащая:
    • заголовок (до 16 байт), и в нём:
      • hash-код
      • ссылку на тип
      • длину массива (для массива)
    • все поля (включая private), объявленные во всех суперклассах
    • или элементы массива (для массива)
  • не содержащая:
    • статические переменные
    • методы
    • другие объекты в себе
    • своё имя (то есть у объекта нет имени)


Подготовка


Здесь приведены результаты оценки памяти объектов разного типа по способу из описания пакета java.lang.instrument (смотри также здесь). Эти результаты позволяют ответить на большинство поставленных выше вопросов.

Необходимо выполнить следующие шаги:

  1. Создать класс-агент, содержащий метод premain:
    public static void premain(String, Instrumentation) {...}
  2. Создать архив, содержащий класс-агент и манифест-файл с содержимым:
    Premain-class: имя-класса-агента
  3. Создать исполняемый класс для оценки памяти.
  4. Указать архив параметром "-javaagent" при запуске виртуальной машины:
    java  -javaagent:имя-архива  имя-исполняемого-класса


Начнём с пробного примера. Для простоты используем безымянный пакет.

Шаг 1. Создаём пробный класс-агент


import java.lang.instrument.Instrumentation;
public class A {
    public static void premain(String notUsedHere, Instrumentation i) {
        System.out.println("premain");
    }
}

Компилируем:

javac A.java

Шаг 2. Создаём манифест-файл m.txt, содержащий:


Premain-class: A
пустая строка

ВНИМАНИЕ: вторая строка файла должна быть ПУСТОЙ, НЕ СОДЕРЖАЩЕЙ ПРОБЕЛОВ.

Создаём архив A.jar:

jar cmf m.txt A.jar A.class

Шаг 3. Создаём пробный исполняемый класс


public class M {
    public static void main(String[] notUsedHere) {
        // Пока без оценки памяти
        System.out.println("main");
    }
}

Компилируем:

javac M.java

Шаг 4. Выполняем


java -javaagent:A.jar M

Результат:
premain
main

указывает, что сначала был вызван метод premain класса-агента, а затем — метод main исполняемого класса.

Теперь создаём требуемый класс-агент:

import java.lang.instrument.Instrumentation;
    public class A {

        // статическую переменную инициализирует виртуальная машина
        private static Instrumentation ins;

        public static void premain(String notUsedHere, Instrumentation i) {
            ins = i;
        }
        public static Instrumentation instrumentation() {return ins;}
    }

и исполняемый класс:

class M {
    public static void main(String[] notUsedHere) {
        mem("Object", new Object());
    }
    private static void mem(Object o, Object ref) {
        System.out.println(o + ": " + objectBytesEstimate(ref));
    }
    private static long objectBytesEstimate(Object ref) {
        if (A.instrumentation() == null) {
            throw new RuntimeException("Not initialized instrumentation.");
        }
        return A.instrumentation().getObjectSize(ref);
    }
}

Метод

long getObjectSize(Object ссылка-на-объект)

возвращает ОЦЕНКУ размера (количества байт) памяти, занимаемой объектом по указанной ссылке. Необходимо иметь ввиду, что полученная оценка может быть иной для иной виртуальной машины. Здесь будут приведены значения для jdk-13.

Выполняем:

javac *.java
jar cmf m.txt A.jar A.class
java -javaagent:A.jar M

и получаем результат:

Object: 16

показывающий, что ПУСТОЙ объект типа Object занимает здесь (ПО ОЦЕНКЕ) 16 байт. Из них 12 байт занимает заголовок, а 4 байта в конце служат для выравнивания длины объекта на границу 8 байт.

Результаты


Дальнейшие примеры будут содержать лишь код, размещаемый в методе main класса M. Их следует выполнять для каждого примера командами:

javac M.java
java -javaagent:A.jar M

Пересоздавать A.jar нет необходимости.

Например, для получения оценки размера памяти объекта произвольного типа без полей, поместим в метод main код:

class C {}; mem("Empty", new C());  // Empty: 16

Результат, указанный в комментарии, показывает, что объект без полей занимает столько же байт, сколько объект типа Object.

Далее, результат программы:


{class C {int a;      } mem(1, new C());} // 1: 16
{class C {int a,b;    } mem(2, new C());} // 2: 24
{class C {int a,b,c;  } mem(3, new C());} // 3: 24
{class C {int a,b,c,d;} mem(4, new C());} // 4: 32

показывает, что каждое int-поле занимает 4 байта. Замечу, что здесь каждая строка — отдельный блок, что позволяет использовать одно и то же имя для разных классов.

Каждое long-поле занимает 8 байт:


{class C {long a;    } mem(1, new C());} // 1: 24
{class C {long a,b;  } mem(2, new C());} // 2: 32
{class C {long a,b,c;} mem(3, new C());} // 3: 40

Каждое boolean-поле занимает 1 байт (для данной ВМ):


{class C {boolean a;        } mem(1, new C());} // 1: 16
{class C {boolean a,b;      } mem(2, new C());} // 2: 16
{class C {boolean a,b,c;    } mem(3, new C());} // 3: 16
{class C {boolean a,b,c,d;  } mem(4, new C());} // 4: 16
{class C {boolean a,b,c,d,e;} mem(5, new C());} // 5: 24

Каждое ссылочное поле занимает 4 байта (для данной ВМ):


{class C {Boolean a;      } mem(1, new C());} // 1: 16
{class C {Integer a;      } mem(1, new C());} // 1: 16
{class C {Long    a;      } mem(1, new C());} // 1: 16
{class C {C       a;      } mem(1, new C());} // 1: 16

{class C {Boolean a,b;    } mem(2, new C());} // 2: 24
{class C {Integer a,b;    } mem(2, new C());} // 2: 24
{class C {Long    a,b;    } mem(2, new C());} // 2: 24
{class C {C       a,b;    } mem(2, new C());} // 2: 24

{class C {Boolean a,b,c;  } mem(3, new C());} // 3: 24
{class C {Integer a,b,c;  } mem(3, new C());} // 3: 24
{class C {Long    a,b,c;  } mem(3, new C());} // 3: 24
{class C {C       a,b,c;  } mem(3, new C());} // 3: 24

{class C {Boolean a,b,c,d;} mem(4, new C());} // 4: 32
{class C {Integer a,b,c,d;} mem(4, new C());} // 4: 32
{class C {Long    a,b,c,d;} mem(4, new C());} // 4: 32
{class C {C       a,b,c,d;} mem(4, new C());} // 4: 32

Поле String-типа тоже занимает 4 байта, как и каждое ссылочное:


{class C {String a;          } mem("  null", new C());}  //   null: 16
{class C {String a="";       } mem(" empty", new C());}  //  empty: 16
{class C {String a="A";      } mem("1-char", new C());}  // 1-char: 16
{class C {String a="1234567";} mem("7-char", new C());}  // 7-char: 16

Поле-ссылка на массив тоже занимаат 4 байта, как и каждое ссылочное:


{class C {int[]   a;                } mem("null", new C());} // null: 16
{class C {int[]   a = {};           } mem("   0", new C());} //    0: 16
{class C {int[]   a = new int[1];   } mem("   1", new C());} //    1: 16
{class C {int[]   a = new int[7];   } mem("   7", new C());} //    7: 16
{class C {int[][] a = {};           } mem("  00", new C());} //   00: 16
{class C {int[][] a = new int[1][1];} mem("  11", new C());} //   11: 16
{class C {int[][] a = new int[7][7];} mem("  77", new C());} //   77: 16

Объект подтипа содержит каждое поле, объявленное в суперклассе, независимо от модификатора доступа:


{class S {               } class C extends S {long a;} mem("0+1", new C());} // 0+1: 24
{class S {private long a;} class C extends S {       } mem("1+0", new C());} // 1+0: 24

Объект подтипа содержит поле, объявленное в суперклассе с тем же именем, что и в подклассе (так называемое спрятанное — hidden):


{class S {       } class C extends S {long a,b;} mem("0+2", new C());} // 0+2: 32
{class S {long a;} class C extends S {long a;  } mem("1+1", new C());} // 1+1: 32

Объект подтипа содержит каждое поле, объявленное в каждом его суперклассе:


class U           {private long a;    }
class S extends U {private long a;    }
class C extends S {        long a;    } mem("1+1+1", new C()); // 1+1+1: 40
class D           {        long a,b,c;} mem("0+0+3", new D()); // 0+0+3: 40

Обратися к массивам. Как известно, массив — это особый вид объекта, элементы которого находятся в самом объекте, так что размер памяти, занимаемый массивом, растёт с числом элементов:


{long[] a = new long[  0]; mem("  0", a);} //   0: 16
{long[] a = new long[  1]; mem("  1", a);} //   1: 24
{long[] a = new long[  2]; mem("  2", a);} //   2: 32
{long[] a = new long[  3]; mem("  3", a);} //   3: 40
{long[] a = new long[100]; mem("100", a);} // 100: 816

А для массива ссылок:


{Long[] a = new Long[  0]; mem("  0", a);} //   0: 16
{Long[] a = new Long[  1]; mem("  1", a);} //   1: 24
{Long[] a = new Long[  2]; mem("  2", a);} //   2: 24
{Long[] a = new Long[  3]; mem("  3", a);} //   3: 32
{Long[] a = new Long[100]; mem("100", a);} // 100: 416

Теперь из любопытства сравним размеры нескольких объектов разного типа:


mem("      Object",      new Object()); //       Object: 16
mem("      String", new String("ABC")); //       String: 24
mem("   Exception",   new Exception()); //    Exception: 40
mem("   int.class",         int.class); //    int.class: 112
mem(" int[].class",       int[].class); //  int[].class: 112
mem("Object.class",      Object.class); // Object.class: 112
mem("System.class",      System.class); // System.class: 160
mem("String.class",      String.class); // String.class: 136

То же для разных jdk на 64-битном процессоре:

                jdk1.6.0_45 jdk1.7.0_80 jdk1.8.0_191 jdk-9  jdk-12 jdk-13
                ----------- ----------- ------------ ------ ------ ------
      Object:   16          16          16           16     16     16
      String:   32          24          24           24     24     24
   Exception:   32          32          32           40     40     40
   int.class:   104         88          104          112    104    112
 int[].class:   584         544         480          112    104    112
Object.class:   600         560         496          112    104    112
System.class:   624         560         496          144    152    160
String.class:   696         640         624          136    128    136

Оценка размера объекта типа String — 24 байта, хотя класс String содержит много статических переменных, статических и нестатических методов. Это несомненно указывает на отсутствие в объекте статических переменных и кода методов. То же верно для объекта любого типа.

В заключение — напоминание: все приведенные данные о размере объекта оценочные и они могут в какой-то мере изменяться от одного выполнения к другому и, конечно, для разных виртуальных машин.
Теги:
Хабы:
Всего голосов 17: ↑16 и ↓1+15
Комментарии6

Публикации