Каков размер конкретной структуры данных? «Могу ли я разместить все эти объекты в моем ehCache ?»
Эта статья является вторым постом в серии, где мы пытаемся ответить на эти вопросы. В последнем посте объясняется разница между сохраняемым и мелким размером объекта. В статье мы также предложили пример того, как рассчитать размер оставшейся кучи структуры данных. В сегодняшней статье мы остановимся на том, что мы называли «простым» в предыдущем посте. А именно — что такое и как измерять мелкую кучу, используемую объектом.
В первом посте мы отбросили всю сложность, заявив, что вычисление мелкого размера кучи легко — оно состоит только из кучи, занятой самим объектом. Но как рассчитать, сколько памяти требует сам объект? Видимо есть формула для этого:
Размер мелкой кучи = [ссылка на определение класса] + пробел для полей суперкласса + пробел для полей экземпляра + [выравнивание]
Не кажется слишком полезным, а? Давайте попробуем применить формулу, используя следующий пример кода:
class X {
int a;
byte b;
java.lang.Integer c = new java.lang.Integer();
} class Y extends X {
java.util.List d;
java.util.Date e;
}
Теперь мы стремимся ответить на вопрос: сколько мелкого размера кучи требуется для экземпляра Y? Давайте начнем вычислять его, предполагая, что мы находимся на 32-битной архитектуре x86:
В качестве отправной точки — Y является подклассом X, поэтому его размер включает «что-то» из суперкласса. Таким образом, прежде чем вычислять размер Y, мы рассмотрим вычисление мелкого размера X.
Если перейти к вычислениям X, первые 8 байтов используются для ссылки на определение класса. Эта ссылка всегда присутствует во всех объектах Java и используется JVM для определения структуры памяти следующего состояния. У этого также есть три переменных экземпляра — целое число, целое число и байт. Эти переменные экземпляра требуют кучи следующим образом:
- Байт — это то, чем он должен быть. 1 байт в памяти.
- int в нашей 32-битной архитектуре требует 4 байта.
- ссылка на Integer также требует 4 байта. Обратите внимание, что при вычислении оставшейся кучи мы также должны учитывать размер примитива, обернутого в объект Integer, но, поскольку мы здесь вычисляем мелкую кучу, мы используем в наших вычислениях только эталонный размер 4 байта.
Так что же? Малая куча X = 8 байт от ссылки на определение класса + 1 байт (байт) + 4 байта (целое число) + 4 байта (ссылка на целое число) = 17 байт? На самом деле — нет . То, что сейчас входит в игру, называется выравниванием (также называемым отступом). Это означает , что JVM выделяет память в упаковке 8 байт, поэтому вместо 17 байт мы выделим 24 байт , если бы создать экземпляр X .
Если бы вы могли следовать за нами до здесь, хорошо, но теперь мы пытаемся сделать вещи еще более сложными. Мы НЕ создаем экземпляр X, а экземпляр Y. Это означает, что мы можем вычесть 8 байтов из ссылки на определение класса и выравнивание. Это может быть не слишком очевидно с первого взгляда, но — вы заметили, что при расчете небольшого размера X мы не учитывали, что он также расширяет java.lang.Object, как это делают все классы, даже если вы явно не указали его в ваш исходный код? Нам не нужно принимать во внимание размеры заголовков суперклассов, потому что JVM достаточно умен, чтобы проверять его в самих определениях классов, вместо того, чтобы постоянно копировать его в заголовки объектов.
То же самое касается выравнивания — при создании объекта выравнивание выполняется только один раз, а не на границах определений суперкласса / подкласса. Поэтому можно с уверенностью сказать, что при создании подкласса для X вы будете наследовать только 9 байтов от переменных экземпляра.
Наконец, мы можем перейти к исходной задаче и начать вычислять размер Y. Как мы видели, мы уже потеряли 9 байтов для полей суперкласса. Давайте посмотрим, что будет добавлено, когда мы на самом деле создадим экземпляр Y.
- Заголовки Y, ссылающиеся на определение класса, занимают 8 байтов. Так же, как и с предыдущими.
- Дата является ссылкой на объект. 4 байта. Легко.
- Список является ссылкой на коллекцию. Опять 4 байта. Trivial.
Таким образом, в дополнение к 9 байтам из суперкласса у нас есть 8 байтов из заголовка, 2 × 4 байта из двух ссылок (Список и Дата). Общий мелкий размер для экземпляра Y будет 25 байтов, которые выровнены по 32.
Чтобы сделать расчеты более простыми, мы агрегировали их на следующей диаграмме:
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | |
Align | Align | Align | Align | |||||||||||||||||||||||||||||
Икс | объект | a | б | с | ||||||||||||||||||||||||||||
Y | объект | a | б | с | d | е |
Что вы можете сделать с этими знаниями? Вместе с навыками расчета размера сохраняемой кучи (описанной в моем недавнем посте ), вы теперь обладаете высочайшей способностью подсчитывать, сколько памяти фактически требуется вашим структурам данных.
Чтобы сделать вещи еще более интересными, мы создали утилиту, которая измеряет размеры как мелкой, так и остаточной кучи для ваших объектов. В самое ближайшее время мы выпустим инструмент для свободного использования. Оставайтесь с нами, подписавшись на нашу ленту Twitter !
PS. При написании этой статьи для вдохновения использовались следующие интернет-ресурсы:
- http://memoryanalyzer.blogspot.com/2010/02/heap-dump-analysis-with-memory-analyzer.html
- http://www.javamex.com/tutorials/memory/object_memory_usage.shtml
- http://www.javamex.com/tutorials/memory/instrumentation.shtml
- http://kohlerm.blogspot.com/2008/12/how-much-memory-is-used-by-my-java.html
- http://www.javaspecialists.eu/archive/Issue142.html