XGM Forum
Сайт - Статьи - Проекты - Ресурсы - Блоги

Форуме в режиме ТОЛЬКО ЧТЕНИЕ. Вы можете задать вопросы в Q/A на сайте, либо создать свой проект или ресурс.
Вернуться   XGM Forum > Другие игры (только чтение)> Minecraft
Ник
Пароль
Войти через VK в один клик
Сайт использует только имя.

 
DioD

offline
Опыт: 45,134
Активность:
Java от RawCode: Сбрасываем шкурки: sun.reflect
Перед основным текстом, я хочу чтобы Вы прочитали замечательную цитату:
Science isn't about why, it's about why not.
она ответит на все ваши вопросы касательно причин появления и смысла этой статьи.
Исходные коды можно читать где угодно, я использую:
  1. Статья про hotstop, еще есть другие виртуальные машины, в том числе вы сами можете написать одну или две...
  1. Чтобы посреди статьи внезапно не возращаться, сразу раскажу, что такое нативный код, что такое нативный класс:
а) Читая исходные тексты некоторых классов которые идут в комплекте, например Class, Runtime, Object и куча других, вы можете обратить внимание на методы, которые объявлены native и не имеют никакого кода....
Эти методы написаны на другом языке и выполняются вне JVM, с исходным кодом этих методов можно ознакомится скачав сорцы hotspot или сорцы сторонней библиотеки, если они конечно есть.
Ваши собственные классы тоже могут вызывать нативный код, этот функционал называется JNI и позволяет делать, что угодно, но только для определённой платформы (или компилировать для всех платформ разом).
Нативные классы не имеют никаких особых указателей, вместо этого они имеют пустой приватный конструктор, при нормальном функционировании инстансы этих классов создаёт сама виртуальная машина, при этом никаких ограничений нет, вплоть до того, что может быть создан класс, который стандартными методами создать не возможно в принципе.
Например инстанс класса Class создать можно только через Unsafe, даже со всеми привелегиями, вам не дадут это сделать стандартными методами.
Некоторые классы которые создаёт виртуальная машина имеют дополнительные поля, которые кроме как через Unsafe не прочитать и не записать, их даже в списке не будет, но они есть.
  1. Основным "окном" в рефлексию является класс AccessibleObject, подклассы которого позволяют получить доступ к полям, методам и конструкторам любого класса, все три являются нативными.
В восьмой версии явы добавились еще несколько, но суть от этого не меняется.
  1. В качестве основного примера, было решено выбрать класс Field, так как он является наиболее интересным (для меня) с практической точки зрения и позволяет читать, что написано в этих самых полях.
Статичные поля являются свойством класса, "просто" поля являются свойством отдельного объекта, но в любом случае вам потребуется получить инстанс класса для работы.
А) Все объекты являются наследником класса Object (даже если это не написано) который имеет финальный нативный метод getClass(), который позволяет выжать из объекта инстанс класса, который этот объект описывает, на любых объектах без исключений, массивы тоже считаются объектами, но с ними всё чуточку не так.
System.out.println(Class.class.getSuperclass());
Б) Если вы знаете полное имя класса, но у вас нет объекта, вы можете получить инстанс класса методом forName(String).
В) Если вы не знаете полное имя класса, но знаете каким класлоадером он загружен, можно пробежатся по полю "classes" этого класслоадера, указанное поле содержит указатели на все классы, которые были загружены.
С) Если никаких зацепок нет - ищите лучше, безопасного поиска по хипу мне не известно, а небезопасный роняет виртуальную машину, совсем, никаких вам эксепшинов.
  1. После того как вы получили инстанс класса, можно вызывать метод getFields() для получения всех* методов или getField(String) для получения метода с конкретным именем, оба метода достаточно сложные, включают в себя ряд проверок безопасности и кешируют результаты, изначально данные о полях исходят от native getDeclaredFields0().
Кеш сохраняется в private volatile transient полях и при доступе к нему предварительно генерируется копия.
Однако при большом желании можно изменить данные кеша и удержать указатель на модифицированный массив (чтобы его не съел сборщик мусора), в этом случае можно будет обмануть стандарную рефлекцию или выдать не те результаты, что ожидает программа.
Так как все эти поля добавлены в фильтр, получить указатель на них без Unsafe нельзя, так что пример будет ниже.
  1. Стандартная рефлексия проверяет доступ к полям, для того чтобы заставить игнорировать ограничения доступа, необходимо применить метод setAccessible(true), часть ограничений не могут быть игнорированы (например вам не разрешат получить доступ к конструктору Class и целому букету полей из соображений стабильности виртуальной машины):
138            if (c.getDeclaringClass() == Class.class) {
139                throw new SecurityException("Can not make a java.lang.Class" +
140                                            " constructor accessible");
141            }
В случае с методами и полями, вы не получите указатель на поле или метод в принципе
42     private static volatile Map<Class,String[]> fieldFilterMap;
43     private static volatile Map<Class,String[]> methodFilterMap;
Used to filter out fields and methods from certain classes from public view, where they are sensitive or they may contain VM-internal objects. These Maps are updated very rarely. Rather than synchronize on each access, we use copy-on-write.
Часть объектов которые находятся в этих самых полях имеют особую структуру не соответствующую декларации ни одного из существующих классов, так что даже если и получить на них указатель, сделать с ними по большей части ничего нельзя, а если их "изменить" то последствия могут быть достаточно весёлые.
Например если сломать объект который хранит блокировки потока, эффект может быть неожиданным:
218 volatile Object parkBlocker;
Еще есть весёлые объекты по разным классам которые лучше не трогать, если нет точных сведений о том что они делают и как.
  1. После того как вы получили указатель на поле и разрешили к нему доступ, можно его читать, для чтения каждого типа предусмотрен отдельный метод, например для чтения 32 бит числа - getInt(Object), для чтения объекта просто get(Object).
Собственно на этой стадии и начинается "волшебство":
515     public int getInt(Object obj)
516         throws IllegalArgumentException, IllegalAccessException
517     {
518         return getFieldAccessor(obj).getInt(obj);
519     }
Код достаточно запутанный, с кучей проверок, которые в принципе не интересны, создание получателя поля есть только в одном месте:
949             tmp = reflectionFactory.newFieldAccessor(this, overrideFinalCheck);
950             setFieldAccessor(tmp, overrideFinalCheck);
Указанный метод после еще пары проверок безопасности выходит на UnsafeFieldAccessorFactory.newFieldAccessor, который в свою очередь выходит на 100500 специализированных классов которые позволяют доставать четыре типа полей на каждый из 9 типов данных (итого 36 разных классов):
А) Static
Б) Volatile
В) Volatile Static
Г) Normal
Самый низкий уровень доступа на данной стадии выглядит вот так:
57     public int getInt(Object obj) throws IllegalArgumentException {
58         ensureObj(obj);
59         return unsafe.getIntVolatile(obj, fieldOffset);
60     }
Как вы видите на этой стадии рефлексия выходит на уровень "Unsafe".
  1. Уровень Unsafe полностью нативный, каждый метод данного класса оборачивает нативный метод JVM, вызовы выглядят как то вот так:
898     public native int     getIntVolatile(Object o, long offset);
На этом уровне нет ни проверок безопасности ни эксепшинов, неправильные действия могут привести к эксепшину виртуальной машины или сразу уронить виртуальную машину целиком (будет бидабида если на этой же виртуальной машине идёт что-то другое), например если вы попытаетесь прочитать память по отрицательному индексу или сделать какую либо другую глупость (гадость).
Вот мы и дошли до финального уровня, а хотя нет, мы ведь еще не смотрели сорцы JVM!
  1. Уровень native - unsafe.cpp
/*
 *      Implementation of class sun.misc.Unsafe
 */
Я ведь упоминал ранее что единственное значимое поле в объекте Field это слот, вот смотрите как hotspot JVM резолвит объект Field в указатель на место хранения, при этом не существует каких либо проверок безопасности, виртуальная машина смотрит валидность данных в объекте (только валидность, а не то что данные залепа), смотрит кому принадлежит поле (классу или объекту) и выдаёт оффсет для этого поля.
jint find_field_offset(jobject field, int must_be_static, TRAPS) {
  if (field == NULL) {
    THROW_0(vmSymbols::java_lang_NullPointerException());
  }

  oop reflected   = JNIHandles::resolve_non_null(field);
  oop mirror      = java_lang_reflect_Field::clazz(reflected);
  klassOop k      = java_lang_Class::as_klassOop(mirror);
  int slot        = java_lang_reflect_Field::slot(reflected);
  int modifiers   = java_lang_reflect_Field::modifiers(reflected);

  if (must_be_static >= 0) {
    int really_is_static = ((modifiers & JVM_ACC_STATIC) != 0);
    if (must_be_static != really_is_static) {
      THROW_0(vmSymbols::java_lang_IllegalArgumentException());
    }
  }

  int offset = instanceKlass::cast(k)->offset_from_fields(slot);
  return field_offset_from_byte_offset(offset);
}

UNSAFE_ENTRY(jlong, Unsafe_ObjectFieldOffset(JNIEnv *env, jobject unsafe, jobject field))
  UnsafeWrapper("Unsafe_ObjectFieldOffset");
  return find_field_offset(field, 0, THREAD);
UNSAFE_END

UNSAFE_ENTRY(jlong, Unsafe_StaticFieldOffset(JNIEnv *env, jobject unsafe, jobject field))
  UnsafeWrapper("Unsafe_StaticFieldOffset");
  return find_field_offset(field, 1, THREAD);
UNSAFE_END
О том как использовать Unsafe будет рассказано отдельно.

Отредактировано DioD, 15.12.2013 в 12:56.
Старый 14.12.2013, 19:24

Опции темы Поиск в этой теме
Поиск в этой теме:

Расширенный поиск

Ваши права в разделе
Вы не можете создавать темы
Вы не можете отвечать на сообщения
Вы не можете прикреплять файлы
Вы можете скачивать файлы

BB-коды Вкл.
[IMG] код Вкл.
HTML код Выкл.
Быстрый переход



Часовой пояс GMT +3, время: 10:47.