Mindprod указывает, что это не простой вопрос:
JVM может свободно хранить данные любым удобным для себя способом, с прямым или прямым порядком байтов, с любым количеством дополнений или накладных расходов, хотя примитивы должны вести себя так, как если бы они имели официальные размеры.
Например, JVM или собственный компилятор может решить хранить boolean[]
в 64-битных длинных чанках, таких как BitSet
. Вам не нужно об этом говорить, если программа дает одинаковые ответы.
- Может выделять некоторые временные объекты в стеке.
- Он может полностью оптимизировать некоторые переменные или вызовы методов, заменяя их константами.
- Он может создавать версии методов или циклов, то есть компилировать две версии метода, каждая из которых оптимизирована для определенной ситуации, а затем заранее решить, какой из них вызвать.
Тогда, конечно, аппаратное обеспечение и ОС имеют многоуровневые кэши на кэш-памяти, кэш-памятью SRAM, кэш-памятью DRAM, обычным рабочим набором RAM и резервным хранилищем на диске. Ваши данные могут дублироваться на каждом уровне кэша. Вся эта сложность означает, что вы можете только очень приблизительно предсказать потребление ОЗУ.
Методы измерения
Вы можете использовать Instrumentation.getObjectSize()
, чтобы получить оценку объема памяти, занимаемой объектом.
Для визуализации фактического макета объекта, площади и ссылок вы можете использовать инструмент JOL (Java Object Layout) .
Заголовки объектов и ссылки на объекты
В современном 64-битном JDK объект имеет 12-байтовый заголовок, дополненный кратным 8 байтам, поэтому минимальный размер объекта составляет 16 байт. Для 32-разрядных JVM служебная информация составляет 8 байтов, дополненная кратным 4 байта. (из ответ Дмитрия Спихальского , ответ Джайена и JavaWorld .)
Как правило, ссылки являются 4 байтами на 32-битных платформах или на 64-битных платформах до -Xmx32G
; и 8 байтов выше 32 ГБ (-Xmx32G
). (См. ссылки на сжатые объекты .)
В результате 64-битная JVM обычно требует на 30-50% больше пространства кучи. ( Должен ли я использовать 32- или 64-разрядную JVM? , 2012, JDK 1.7)
Типы, массивы и строки в штучной упаковке
Оболочки в штучной упаковке имеют накладные расходы по сравнению с примитивными типами (из JavaWorld ):
Integer
: 16-байтовый результат немного хуже, чем я ожидал, поскольку значение int
может уместиться всего в 4 дополнительных байта. Использование Integer
обойдется мне в 300 процентов дополнительной памяти по сравнению с тем, когда я могу сохранить значение как примитивный тип
Long
: также 16 байтов: Очевидно, что фактический размер объекта в куче зависит от низкоуровневого выравнивания памяти, выполняемого конкретной реализацией JVM для конкретного типа процессора. Похоже, что Long
составляет 8 байтов служебных данных объекта плюс еще 8 байтов для фактического длинного значения. В отличие от этого, Integer
имел неиспользуемое 4-байтовое отверстие, скорее всего потому, что JVM I использует принудительное выравнивание объекта на границе 8-байтового слова.
Другие контейнеры тоже дороги:
Многомерные массивы : это еще один сюрприз.
Разработчики обычно используют конструкции типа int[dim1][dim2]
в численных и научных вычислениях.
В экземпляре массива int[dim1][dim2]
каждый вложенный массив int[dim2]
является самостоятельным Object
. Каждый добавляет обычные 16-байтовые издержки массива. Когда мне не нужен треугольный или рваный массив, это означает чистые накладные расходы. Воздействие возрастает, когда размеры массива сильно различаются.
Например, экземпляр int[128][2]
занимает 3600 байт. По сравнению с 1040 байтами, используемыми экземпляром int[256]
(который имеет такую же емкость), 3600 байтов представляют 246-процентную служебную нагрузку. В крайнем случае byte[256][1]
коэффициент накладных расходов составляет почти 19! Сравните это с ситуацией C / C ++, в которой тот же синтаксис не добавляет никаких затрат на хранение.
String
: увеличение памяти String
отслеживает рост его внутреннего массива символов. Однако класс String
добавляет еще 24 байта служебной информации.
Для непустых String
размером не более 10 символов добавленные накладные расходы относительно полезной нагрузки (2 байта для каждого символа плюс 4 байта для длины) варьируются от 100 до 400 процентов.
Выравнивание
Рассмотрим этот пример объекта :
class X { // 8 bytes for reference to the class definition
int a; // 4 bytes
byte b; // 1 byte
Integer c = new Integer(); // 4 bytes for a reference
}
Наивная сумма предполагает, что экземпляр X
будет использовать 17 байтов. Однако из-за выравнивания (также называемого заполнением) JVM выделяет память кратно 8 байтам, поэтому вместо 17 байт будет выделено 24 байта.