Как я могу изменить класс java.lang на лету? - программирование
Подтвердить что ты не робот

Как я могу изменить класс java.lang на лету?

Я ищу способ добавления полей в поток на лету, переписывая байтовый код и перезагружая класс, не уверен, что это вообще возможно. Любые указатели приветствуются. Я нашел некоторую информацию об изменении и загрузке класса, и я знаю, что JRebel может без проблем плавно перекопировать ваш код, но не уверен, что здесь применяются те же подходы/инструменты.

Мотивация здесь заключается в изучении теоретически лучшей альтернативы потоковым локальным объектам. Если этот метод работает, я должен иметь возможность заменить thread local аннотацией, и результат должен превзойти текущую реализацию JDK.

PS: Пожалуйста, спаси меня "корень всей злой речи"

Уточнение прецедента:

Представьте, что у меня есть класс с ThreadLocal:


class A {
   ThreadLocal&ltCounter&gt counter;
   ...
   counter.get().inc()
}

Я хотел бы заменить это аннотацией:


class A {
   @ThreadLocal
   Counter counter;
   ...
   counter.inc()
}

Но вместо того, чтобы генерируемый выше код был сгенерирован, я хотел бы мутировать Thread таким образом, что Thread теперь будет иметь поле Acounter, а фактический код будет:


class A {
   // Nothing here, field is now in Thread
   ...
   Thread.currentThread().Acounter.inc()
}
4b9b3361

Ответ 1

В настоящее время невозможно переопределить класс во время выполнения, так что переопределение приведет к новым методам или полям. Это связано с сложностью, связанной с сканированием кучи для всех существующих экземпляров и их преобразованием + их ссылками + потенциалом. Невосприимчивые обновления для смещения полей (например, AtomicFieldUpdater).

Это ограничение может быть отменено как часть JEP-159, но, как обсуждалось в группе рассылки concurrency -interest, это большое изменение воздействия, возможно, никогда не произойдет вообще.

Использование Javaassist/аналогичное позволит преобразовать класс в новый класс с новыми методами/полями. Этот класс может быть загружен ClassLoader и использоваться во время выполнения, но это определение не заменит существующие экземпляры. Таким образом, невозможно будет использовать этот метод в сочетании с агентом, чтобы переопределить класс, поскольку переопределение инструментария ограничено, так что: "переопределение может изменить тела методов, пул констант и атрибуты. Переопределение не должно добавлять, удалять или переименовывать поля..." см. здесь.

Итак, теперь NO.

Ответ 2

Если вы хотите изменить поведение класса во время выполнения, вы можете попробовать javassist. API здесь

Ответ 3

Я видел пользовательское решение загрузки классов, которое динамически перезагружает JAR - вы определяете один ClassLoader на JAR файл и используете его для загрузки классов из этого JAR; для перезагрузки всего JAR вы просто "убиваете" свой экземпляр ClassLoader и создаете еще один (после замены JAR файла).

Я не думаю, что можно настроить внутренний класс Java Thread таким образом, потому что у вас нет контроля над System ClassLoader. Возможное решение состоит в том, чтобы иметь класс CustomThreadWeaver, который будет генерировать новый класс, расширяющий Thread с необходимыми переменными, и использовать пользовательский DynamicWeavedThreadClassLoader для их загрузки.

Удачи и покажите нам своего монстра, когда вы преуспеете, -)

Ответ 4

Возможные используя инструменты и, возможно, библиотеки, такие как javassist, для изменения кода на лету. (однако добавление и удаление полей, методов или конструкторов в настоящее время невозможно)

//Modify code using javassist and call CtClass#toBytecode() or load bytecode from file
byte[] nevcode;
Class<?> clz = Class.forName("any.class.Example");
instrumentationInstace.redefineClasses(new ClassDefinition(clz, nevcode));

Не забудьте добавить Can-Redefine-Classes: true в манифест агента Java.

Реальный пример - оптимизация java < 9 string.replace(CharSequence, CharSequence) с использованием javassist:

String replace_src = 
    "{String str_obj = this;\n"
    + "char[] str = this.value;\n"
    + "String find_obj = $1.toString();\n"
    + "char[] find = find_obj.value;\n"
    + "String repl_obj = $2.toString();\n"
    + "char[] repl = repl_obj.value;\n"
    + "\n"
    + "if(str.length == 0 || find.length == 0 || find.length > str.length) {\n"
    + "    return str_obj;\n"
    + "}\n"
    + "int start = 0;\n"
    + "int end = str_obj.indexOf(find_obj, start);\n"
    + "if(end == -1) {\n"
    + "    return str_obj;\n"
    + "}\n"
    + "int inc = repl.length - find.length;\n"
    + "int inc2 = str.length / find.length / 512;\ninc2 = ((inc2 < 16) ? 16 : inc);\n"
    + "int sb_len = str.length + ((inc < 0) ? 0 : (inc * inc2));\n"
    + "StringBuilder sb = (sb_len < 0) ? new StringBuilder(str.length) : new StringBuilder(sb_len);\n"
    + "while(end != -1) {\n"
    + "    sb.append(str, start, end - start);\n"
    + "    sb.append(repl);\n"
    + "    start = end + find.length;\n"
    + "    end = str_obj.indexOf(find_obj, start);\n"
    + "}\n"
    + "if(start != str.length) {\n"
    + "    sb.append(str, start, str.length - start);\n"
    + "}\n"
    + "return sb.toString();\n"
    +"}";


ClassPool cp = new ClassPool(true);
CtClass clz = cp.get("java.lang.String");
CtClass charseq = cp.get("java.lang.CharSequence");

clz.getDeclaredMethod("replace", new CtClass[] {
        charseq, charseq
}).setBody(replace_src);

instrumentationInstance.redefineClasses(new ClassDefinition(Class.forName(clz.getName(), false, null), clz.toBytecode()));

Ответ 5

Кажется, это вопрос использования правильного инструмента для работы. Здесь задан аналогичный вопрос: Другой вопрос и библиотека манипулирования байтом кода Javaassist - это возможное решение.

Но без дальнейших подробностей о причинах, по которым это делается, кажется, что реальный ответ заключается в использовании правильного инструмента для работы. Например, с Groovy возможностью динамически добавлять методы на язык.

Ответ 6

Вы можете попробовать создать агент JVM, который использует java.lang.instrument API и более конкретно использовать метод повторной передачи, который "облегчает инструментарий уже загруженных классов", а затем использует Javassist (или ASM), как упоминалось для работы с байт-кодом.

Дополнительная информация о java.lang.instrument API

Ответ 7

Чтобы сделать то, что вам нужно, более простой альтернативой будет использование подкласса Thread, его запуск, а затем внутри этого потока выполнить код из вашего примера (вместе с литом currentThread() в ваш подкласс).

Ответ 8

То, что вы пытаетесь сделать, невозможно.

Поскольку вы уже знаете о ThreadLocal, вы уже знаете, что такое предлагаемое решение.

Кроме того, вы можете подклассифицировать Thread и добавить свои собственные поля; однако только те потоки, которые вы явно создаете из этого класса, будут иметь эти поля, поэтому вам все равно придется "откидываться" на использование локального потока.

Реальный вопрос: "почему?", как в "Почему локальный поток недостаточен для ваших требований?"