Под впечатлениями от 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 байтам.
Писал в спешке. Исправления, вопросы, пожелания
