При настройке JVM мы часто обращаем внимание на размер каждой области JVM и связанные параметры, чтобы выполнить определенные оптимизации.При устранении проблемы с переполнением памяти я не могу не задать вопрос, как сколько памяти занимает объект Java? Далее будем анализировать и проверять.
Структура памяти объектов Java
В JVM объекты Java размещаются в памяти кучи. Чтобы проанализировать использование памяти объектами Java, вы должны сначала понять структуру памяти объектов Java. Занятие памяти объектом Java состоит из трех частей:对象头(Header)
,实例数据(Instance Data)
и对齐填充(Padding)
.
Заголовок объекта (Заголовок)
Состав заголовка объекта
Заголовок объекта виртуальной машины включает в себя две части информации, первая часть используется для хранения对象自身的运行时数据
,какhashCode
,GC分代年龄
,锁状态标志
,线程持有的锁
,偏向线程ID
,偏向时间戳
Ждать. Длина этой части данных составляет 4Б и 8Б в 32-битных и 64-битных виртуальных машинах (без сжатия указателя поворота), а официальная называется”Mark Word”
.
Другая часть объекта类型指针(kclass)
, то есть указатель объекта на его метаданные класса, и виртуальная машина использует этот указатель, чтобы определить, что объект является экземпляром этого класса. Кроме того, если объект представляет собой массив Java, в заголовке объекта также должен быть фрагмент данных для записи длины массива, потому что виртуальная машина может определить размер объекта Java через информацию метаданных обычного Java объект, но из метаданных массива Но размер массива не может быть определен. Точно так же длина этой части данных составляет 4 и 8 байт в 32-битных и 64-битных виртуальных машинах (без включенного сжатия указателя).
сжатие указателя
Начиная с обновления JDK 1.614, официально поддерживается 64-битная JVM.-XX:+UseCompressedOops
Это может сжимать указатель, новый параметр, который экономит использование памяти.
еслиUseCompressedOops
открыт, указатели на следующие объекты сжимаются:
所有对象的 klass 属性
所有对象指针实例的属性
所有对象指针数组的元素(objArray)
Отсюда мы можем рассчитать размер заголовка объекта:
32位虚拟机对象头大小= Mark Word(4B)+ kclass(4B) = 8B
64位虚拟机对象头大小= Mark Word(8B)+ kclass(4B) = 12B
данные экземпляра
Данные экземпляра в объекте Java могут включать два типа, один из 8 основных типов, а другой данные экземпляра также являются объектом.У многих людей может быть неправильное понимание:
базовый тип? Разве примитивные типы не размещены в стеке? Как рассчитать размер объекта, выделенного в куче памяти?
Выделяют ли примитивные типы память в стеке? На самом деле это не так. Так называемая "стековая память сохраняет базовые типы и ссылки на объекты, а куча память сохраняет объекты" - это просто неточное предложение. На самом деле, после тщательного изучения, стековая память (более профессиональный термин ) используется как виртуальная машина в качестве метода Структура данных вызова и выполнения метода может содержать пять видов информации:
局部变量表
操作数栈
动态链接
方法返回地址
附加信息
В таблице локальных переменных хранятся локальные переменные в методе, которые могут быть 8 базовых типов или эталонными.
То есть тип базового стека памяти хранения, локальные переменные являются методом и если базовый тип как переменные экземпляра объекта, пространство выделяется в куче, кроме того, если конечная переменная экземпляра изменяется, ни в do не пространство стека, выделенное в куче, а назначенное постоянному пулу внутри.
8 основных типов и эталонный размер фиксированы на виртуальной машине, см. таблицу ниже.
Primitive Type | Memory Required(bytes) |
---|---|
boolean | 1 |
byte | 1 |
short | 2 |
char | 2 |
int | 4 |
float | 4 |
long | 8 |
double | 8 |
Reference | 4 |
Прокладка
Поскольку система управления памятью виртуальной машины требует, чтобы начальный адрес памяти объекта Java был целым числом, кратным 8, иными словами, размер объекта Java должен быть целым числом, кратным 8. При размере заголовка объекта + экземпляра data не является целым числом, кратным 8, для заполнения будет использоваться механизм padding.Например, фактический размер new Object() на 64-битной виртуальной машине:
Mark Word (8B) + kclass(4B) [сжатие открытого указателя] = 12B
Но из-за механизма заполнения фактическое занимаемое пространство: Mark Word(8B)+kclass(4B)[сжатие открытого указателя]+Padding(4B) = 16B
размер массива
Массив в Java также является объектом.По сравнению с обычными объектами Java, размер массива имеет больше информации о длине массива (4B), то есть размер объекта массива Mark Word (8B) + kclass(4B) [сжатие открытого указателя] + длина массива (4B) = 16B
Вычислите размер объекта Java, используя инструментарий
Теперь, когда мы знаем размер объекта Java = заголовок объекта + данные экземпляра + заполнение, теперь давайте проверим результат расчета, google кInstrumentation
Достаточно, чтобы рассчитать размер объекта
Instrumentation
это функция, представленная в Java SE 5, использующаяInstrumentation
, разработчики могут создать независимый от приложения агент (агент) для мониторинга и помощи программам, работающим на JVM, и даже реализовать методы модификации байт-кода. Проще говоря,Instrumentation
Реализован АОП на уровне виртуальной машины.
Эта статья не охватываетInstrumentation
сложные приложения, мы используем толькоInstrumentation
один изgetObjectSize()
метод для получения размера объекта.
использоватьInstrumentation
Требует использования технологии javaagent,
Проще говоря, при запуске класса с основной функцией вы можете указать конкретный файл jar (содержащий параметр –javaagent)Instrumentation
агент) для началаInstrumentation
Агент. В частности, разделить на три этапа:
Класс письма как прокси-инструментарий
Среди них premain вводится в Instrumentation, а sizeOf используется для вычисления пространства, занимаемого объектом.
ObjectShallowSize.java:
package sizeof;
import java.lang.instrument.Instrumentation;
public class ObjectShallowSize {
private static Instrumentation inst;
public static void premain(String agentArgs, Instrumentation instP){
inst = instP;
}
public static long sizeOf(Object obj){
return inst.getObjectSize(obj);
}
}
две упаковки
Создайте новый /META-INF/MANIFEST.MF по пути ObjectShallowSize.java и укажите Premain-Class Содержание:
Manifest-Version: 1.0
Premain-Class: sizeof.ObjectShallowSize
затем скомпилируйте и упакуйте
javac -d . ObjectShallowSize.java
jar cvfm java-agent-sizeof.jar META-INF/MANIFEST.MF .
три пробега
Напишите класс тестового шаблона ObjectSizeTest.java , используйте
java -javaagent:java-agent-sizeof.jar ObjectSizeTest
Запустить программу
Код ObjectSizeTest.java выглядит следующим образом:
package sizeof;
public class ObjectSizeTest {
public static void main(String[] args) {
System.out.println(ObjectShallowSize.sizeOf(new ObjectSizeTest));
}
}
ObjectSizeTest не имеет переменных экземпляра, теоретический расчет
Размер ObjectSizeTest = Mark Word (8B) + kclass (4B) [сжатие открытого указателя] + Padding (4B) = 16B
Для удобства проверим это в IDEA, прямо сейчас импортируем класс ObjectSizeTest и укажем параметры JVM, как показано на рисунке
Текущий результат — 16B, что согласуется с нашим предположением.
Затем мы добавляем несколько переменных экземпляра в класс шаблона, чтобы проверить
один
package sizeof;
public class ObjectSizeTest {
private int i;
public static void main(String[] args) {
System.out.println(ObjectShallowSize.sizeOf(new ObjectSizeTest()));
}
}
Теоретическое значение: Mark Word (8B) + kclass(4B) + i(4B) =16B
Реальная стоимость:16B
два
package sizeof;
public class ObjectSizeTest {
private int i;
private int j;
public static void main(String[] args) {
System.out.println(ObjectShallowSize.sizeOf(new ObjectSizeTest()));
}
}
Теоретическое значение: Mark Word (8B) + kclass(4B) + i(4B) + j(4B)+Padding(4B) =24B
Реальная стоимость:24B
три
package sizeof;
public class ObjectSizeTest {
private int i;
private int j;
private String s;
private boolean aBoolean;
private char c;
public static void main(String[] args) {
System.out.println(ObjectShallowSize.sizeOf(new ObjectSizeTest()));
}
}
Теоретическое значение: Mark Word (8b) + KClass (4b) + I (4b) + j (4b) + S (4b) + abouraean (1b) + C (2b) + paddding (5b) =32B
Реальная стоимость:32B
Четыре
package sizeof;
public class ObjectSizeTest {
private String s; // 4
private int i1; // 4
private byte b1; // 1
private byte b2; // 1
private int i2;// 4
private Object obj; //4
private byte b3; // 1
public static void main(String[] args) {
System.out.println(ObjectShallowSize.sizeOf(new ObjectSizeTest()));
}
}
Теоретическое значение: Mark Word(8B) + kclass(4B) + s(4B) + i1(4B) + b1(1B) + b2(1B) + 2(padding) + i2(4B) + obj(4B)+ b3 (1B) + Заполнение (7B) =40B
Реальная стоимость:32B
Нани? Почему теоретическое значение и фактическое значение здесь несовместимы?
На самом деле поля объектов, созданных HotSpot, будут располагаться в заданном порядке первыми, порядок по умолчанию следующий, от длинного к короткому, а ссылка последняя: long/double --> int/float --> короткая/символьная --> байтовая/логическая --> ссылка
Этот порядок можно использовать с аргументами JVM:-XX:FieldsAllocationSylte = 0
(по умолчанию 1) для изменения.
Таким образом, мы пересчитываем размер объекта
Пометить слово(8B) + kclass(4B) + i1(4B) + i2(4B) + b1(1B) + b2(1B) + b3(1B) + заполнение(1B) + s(4B) + obj(4B) = 32В
соответствует ожидаемому значению.
Фактический размер объекта Java
Когда мы вычисляли размер объекта Java ранее, например, переменные, которые являются объектами, вычислялся только размер их ссылки.На самом деле, сами переменные экземпляра также должны быть включены.Мы можем вынести переменные экземпляра в объект Java через механизм отражения, а также рекурсивно вычислять и накапливать их реальный размер.Moonlit Phire Field.iteye.com/blog/203304...Он предоставил следующие установленные процедуры, используя метод fullSizeOf() для вычисления фактического размера объекта Java.
import java.lang.instrument.Instrumentation;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashSet;
import java.util.Set;
/**
* 对象占用字节大小工具类
* <p>
*
* @author tianmai.fh
* @date 2014-03-18 11:29
*/
public class SizeOfObject {
static Instrumentation inst;
public static void premain(String args, Instrumentation instP) {
inst = instP;
}
/**
* 直接计算当前对象占用空间大小,包括当前类及超类的基本类型实例字段大小、<br></br>
* 引用类型实例字段引用大小、实例基本类型数组总占用空间、实例引用类型数组引用本身占用空间大小;<br></br>
* 但是不包括超类继承下来的和当前类声明的实例引用字段的对象本身的大小、实例引用数组引用的对象本身的大小 <br></br>
*
* @param obj
* @return
*/
public static long sizeOf(Object obj) {
return inst.getObjectSize(obj);
}
/**
* 递归计算当前对象占用空间总大小,包括当前类和超类的实例字段大小以及实例字段引用对象大小
*
* @param objP
* @return
* @throws IllegalAccessException
*/
public static long fullSizeOf(Object objP) throws IllegalAccessException {
Set<Object> visited = new HashSet<Object>();
Deque<Object> toBeQueue = new ArrayDeque<Object>();
toBeQueue.add(objP);
long size = 0L;
while (toBeQueue.size() > 0) {
Object obj = toBeQueue.poll();
//sizeOf的时候已经计基本类型和引用的长度,包括数组
size += skipObject(visited, obj) ? 0L : sizeOf(obj);
Class<?> tmpObjClass = obj.getClass();
if (tmpObjClass.isArray()) {
//[I , [F 基本类型名字长度是2
if (tmpObjClass.getName().length() > 2) {
for (int i = 0, len = Array.getLength(obj); i < len; i++) {
Object tmp = Array.get(obj, i);
if (tmp != null) {
//非基本类型需要深度遍历其对象
toBeQueue.add(Array.get(obj, i));
}
}
}
} else {
while (tmpObjClass != null) {
Field[] fields = tmpObjClass.getDeclaredFields();
for (Field field : fields) {
if (Modifier.isStatic(field.getModifiers()) //静态不计
|| field.getType().isPrimitive()) { //基本类型不重复计
continue;
}
field.setAccessible(true);
Object fieldValue = field.get(obj);
if (fieldValue == null) {
continue;
}
toBeQueue.add(fieldValue);
}
tmpObjClass = tmpObjClass.getSuperclass();
}
}
}
return size;
}
/**
* String.intern的对象不计;计算过的不计,也避免死循环
*
* @param visited
* @param obj
* @return
*/
static boolean skipObject(Set<Object> visited, Object obj) {
if (obj instanceof String && obj == ((String) obj).intern()) {
return true;
}
return visited.contains(obj);
}
}
Использованная литература:
- "Глубокое понимание виртуальной машины Java"
Спасибо за прочтение, оригинальность не из легких, если вы вдохновились, ставьте лайк! Это будет сильнейшей мотивацией для моего письма! Эта статья не публикуется одновременно на техническом паблике, который не ограничивается технологиями.
Nauyus
, В основном делитесь некоторыми языками программирования, архитектурным дизайном, статьями по мышлению и познанию, начиная с режима еженедельного обновления с декабря 2019 года, добро пожаловать, чтобы обратить внимание, учиться и расти вместе!