Под впечатлениями от habrahabr.ru/blogs/java/134102.
Недавно мне приходилось немного поковыряться внутри JVM. Довольно интересный опыт. Текст в вышеупомянутом топике не совсем сходится с моим опытом, но я не считаю себя обладателем абсолютной истины. Ниже я поделюсь с читателями небольшой частью моих экспериментов, которые касаются непосредственно объектов Java.
Windows XP SP3 32bit
D:\dev\puzzles>java -version
java version «1.6.0_29»
Java(TM) SE Runtime Environment (build 1.6.0_29-b11)
Java HotSpot(TM) Client VM (build 20.4-b02, mixed mode, sharing)
Все эксперименты проводились с использованием утилитарного класса Unsafe. Описание класса нагло украдено с wasm.ru.
Малоизвестный класс sun.misc.Unsafe входит в комплект Sun Java Runtime начиная с первых версий. Как и все остальные классы в package sun.*, Unsafe не документирован, но имена (в большинстве своем нативных) функций, видимые при декомпиляции, говорят сами за себя. Явно присутствуют функции работы с памятью (allocateMemory, freeMemory,...), чтения и записи значений по заданному адресу (putLong, getLong,...) и некоторые более специализированные (throwException, monitorEnter, ...).
Но, так просто инстанциировать Unsafe не удасться. Единственный конструктор — приватный, а в getUnsafe() проверяется загрузчик вызвавшего класса и объект возвращается только если класс загружен через Bootloader. В противном случае, получаем SecurityException.
К счастью существует еще внутренняя переменная theUnsafe, до которой можно добраться с помощью Reflection. Я собрал небольшой вспомогательный класс.
Нам понадобятся операции взятия объекта по адресу и взятия адреса у объекта: o2a(Object o) и a2o(long address).
Теперь можно что-то смотреть.
Рассмотрим работу следующего кода:
Данный код представляет собой класс, содержащий 2 приватных поля (int и Integer). Мы создаем экземпляр этого класса, узнаем его адрес в памяти, и выводим 28 байт.
Его результат в моем случае был следующим (я отформатировал вывод группами по 4 байта для большей ясности):
Итак, что мы видим:
01 00 00 00 — это так называемый magic. Это маска объекта. Хранит различную информацию, например о локах. Подробнее в комментариях.
88 D7 79 32 — забегая вперед скажу, что это ссылка на объект класса.
02 00 00 00 — это значение нашего примитивного поля типа int.
D0 7C E4 22 — опять же, забегая вперед, скажу, что это ссылка на объект поля типа Integer.
А дальше… дальше это уже другой объект, лежащий рядом в памяти. Немного изменив наш код, можно это проверить.
Результат:
В этом примере мы взяли адрес поля типа Integer, а затем взяли объект, лежащий по этому адресу. Результат подтвердил наши предположения.
Следующий пример подтвердит наши ожидания, касательно второго блока (адрес класса).
Результат:
В этом примере я убрал ссылочные поля из класса. Создал 2 объекта, в одном из которых поменял значения полей. И, как результат, объекты попали в память рядом, и их можно отличить по значению примитивных полей.
Еще один пример с массивом:
Массивы действительно хранят количество элементов в нем.
Пример со смещением:
Здесь я оставил у класса только одно поле.
Результат:
Здесь видно, что за ссылкой на значение поля типа Integer идет пустой блок из 4 байт. Все объекты дополняются до размера, кратного 8 байтам.
Пример со смещением:
Здесь рассматриваемый объект находится в блоке синхронизации, в итоге значение первых 4 байт изменилось.
Результат:
Java объект состоит из:
4 байта — magic маска.
4 байта — адрес класса.
4 байта — размер массива (только в случае массивов разумеется).
n * 4 байта — на каждое поле объекта (значение примитивного типа или ссылка на объект).
Все это дополняется до кратности 8 байтам.
Писал в спешке. Исправления, вопросы, пожеланияи лучи ненависти приветствуются.
Недавно мне приходилось немного поковыряться внутри JVM. Довольно интересный опыт. Текст в вышеупомянутом топике не совсем сходится с моим опытом, но я не считаю себя обладателем абсолютной истины. Ниже я поделюсь с читателями небольшой частью моих экспериментов, которые касаются непосредственно объектов Java.
Тестовая система
Windows XP SP3 32bit
D:\dev\puzzles>java -version
java version «1.6.0_29»
Java(TM) SE Runtime Environment (build 1.6.0_29-b11)
Java HotSpot(TM) Client VM (build 20.4-b02, mixed mode, sharing)
Подготовка
Все эксперименты проводились с использованием утилитарного класса Unsafe. Описание класса нагло украдено с wasm.ru.
Малоизвестный класс sun.misc.Unsafe входит в комплект Sun Java Runtime начиная с первых версий. Как и все остальные классы в package sun.*, Unsafe не документирован, но имена (в большинстве своем нативных) функций, видимые при декомпиляции, говорят сами за себя. Явно присутствуют функции работы с памятью (allocateMemory, freeMemory,...), чтения и записи значений по заданному адресу (putLong, getLong,...) и некоторые более специализированные (throwException, monitorEnter, ...).
Но, так просто инстанциировать Unsafe не удасться. Единственный конструктор — приватный, а в getUnsafe() проверяется загрузчик вызвавшего класса и объект возвращается только если класс загружен через Bootloader. В противном случае, получаем SecurityException.
public static Unsafe getUnsafe() {
Class class1 = Reflection.getCallerClass(2);
if (class1.getClassLoader() != null)
throw new SecurityException("Unsafe");
else
return theUnsafe;
}
К счастью существует еще внутренняя переменная theUnsafe, до которой можно добраться с помощью Reflection. Я собрал небольшой вспомогательный класс.
import java.lang.reflect.Field;
import sun.misc.Unsafe;
public class T {
public static Unsafe u;
private static long fieldOffset;
private static T instance = new T();
private Object obj;
static {
try {
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
u = (Unsafe) f.get(null);
fieldOffset = u.objectFieldOffset(T.class.getDeclaredField("obj"));
} catch (Exception ex) {
throw new RuntimeException(ex);
}
};
public synchronized static long o2a(Object o) {
instance.obj = o;
return u.getLong(instance, fieldOffset);
}
public synchronized static Object a2o(long address) {
u.putLong(instance, fieldOffset, address);
return instance.obj;
}
public static Unsafe get() {
return u;
}
}
Нам понадобятся операции взятия объекта по адресу и взятия адреса у объекта: o2a(Object o) и a2o(long address).
Теперь можно что-то смотреть.
Структура объекта
Рассмотрим работу следующего кода:
import sun.misc.Unsafe;
public class V {
private Integer b = 3;
private int a = 2;
public static void main(String[] argv) throws Exception {
Unsafe u = T.get();
long obj = T.o2a(new V());
for (int i = 0; i < 28; i++)
System.out.print(u.getByte(obj + i) + " ");
}
}
Данный код представляет собой класс, содержащий 2 приватных поля (int и Integer). Мы создаем экземпляр этого класса, узнаем его адрес в памяти, и выводим 28 байт.
Его результат в моем случае был следующим (я отформатировал вывод группами по 4 байта для большей ясности):
01 00 00 00
88 D7 79 32
02 00 00 00
D0 7C E4 22
01 00 00 00
E8 09 19 37
68 6F E4 22
Итак, что мы видим:
01 00 00 00 — это так называемый magic. Это маска объекта. Хранит различную информацию, например о локах. Подробнее в комментариях.
88 D7 79 32 — забегая вперед скажу, что это ссылка на объект класса.
02 00 00 00 — это значение нашего примитивного поля типа int.
D0 7C E4 22 — опять же, забегая вперед, скажу, что это ссылка на объект поля типа Integer.
А дальше… дальше это уже другой объект, лежащий рядом в памяти. Немного изменив наш код, можно это проверить.
import sun.misc.Unsafe;
public class V {
private int a = 2;
private Integer b = 777;
public static void main(String[] argv) throws Exception {
Unsafe u = T.get();
long obj = T.o2a(new V());
for (int i = 0; i < 28; i++)
System.out.print(u.getByte(obj + i) + " ");
long field = u.getAddress(obj + 3 * 4);
Object i = T.a2o(field);
System.out.print("\nInteger: " + i);
}
}
Результат:
01 00 00 00
98 D6 79 32
02 00 00 00
C8 FB E4 22
...
Integer: 777
В этом примере мы взяли адрес поля типа Integer, а затем взяли объект, лежащий по этому адресу. Результат подтвердил наши предположения.
Следующий пример подтвердит наши ожидания, касательно второго блока (адрес класса).
import sun.misc.Unsafe;
public class V {
private int a = 2;
private int b = 3;
public static void main(String[] argv) throws Exception {
Unsafe u = T.get();
long obj = T.o2a(new V());
V v = new V();
v.a = 5;
v.b = 6;
for (int i = 0; i < 32; i++)
System.out.print(u.getByte(obj + i) + " ");
}
}
Результат:
01 00 00 00
08 D6 79 32
02 00 00 00
03 00 00 00
01 00 00 00
08 D6 79 32
05 00 00 00
06 00 00 00
В этом примере я убрал ссылочные поля из класса. Создал 2 объекта, в одном из которых поменял значения полей. И, как результат, объекты попали в память рядом, и их можно отличить по значению примитивных полей.
Еще один пример с массивом:
import sun.misc.Unsafe;
public class V {
public static void main(String[] argv) throws Exception {
Unsafe u = T.get();
long obj = T.o2a(new Integer[] {1, 2, 3});
for (int i = 0; i < 28; i++)
System.out.print(u.getByte(obj + i) + " ");
}
}
01 00 00 00
B8 C9 79 32
03 00 00 00
40 78 E4 22
50 78 E4 22
60 78 E4 22
...
Массивы действительно хранят количество элементов в нем.
Пример со смещением:
import sun.misc.Unsafe;
public class V {
private Integer a = 1;
public static void main(String[] argv) throws Exception {
Unsafe u = T.get();
long obj = T.o2a(new V());
for (int i = 0; i < 28; i++)
System.out.print(u.getByte(obj + i) + " ");
}
}
Здесь я оставил у класса только одно поле.
Результат:
01 00 00 00
B0 D7 79 32
60 78 E4 22
00 00 00 00
01 00 00 00
E8 09 19 37
...
Здесь видно, что за ссылкой на значение поля типа Integer идет пустой блок из 4 байт. Все объекты дополняются до размера, кратного 8 байтам.
Пример со смещением:
import sun.misc.Unsafe;
public class V {
private int a = 2;
private int b = 3;
public static void main(String[] argv) throws Exception {
Unsafe u = T.get();
V v = new V();
synchronized (v) {
long obj = T.o2a(v);
for (int i = 0; i < 32; i++)
System.out.print(T.hex(u.getByte(obj + i)) + " ");
}
}
}
Здесь рассматриваемый объект находится в блоке синхронизации, в итоге значение первых 4 байт изменилось.
Результат:
8C 84 F0 00
58 D6 79 32
02 00 00 00
03 00 00 00
...
Выводы
Java объект состоит из:
4 байта — magic маска.
4 байта — адрес класса.
4 байта — размер массива (только в случае массивов разумеется).
n * 4 байта — на каждое поле объекта (значение примитивного типа или ссылка на объект).
Все это дополняется до кратности 8 байтам.
Писал в спешке. Исправления, вопросы, пожелания