Как я могу изменить класс java.lang на лету?
Я ищу способ добавления полей в поток на лету, переписывая байтовый код и перезагружая класс, не уверен, что это вообще возможно. Любые указатели приветствуются. Я нашел некоторую информацию об изменении и загрузке класса, и я знаю, что JRebel может без проблем плавно перекопировать ваш код, но не уверен, что здесь применяются те же подходы/инструменты.
Мотивация здесь заключается в изучении теоретически лучшей альтернативы потоковым локальным объектам. Если этот метод работает, я должен иметь возможность заменить thread local аннотацией, и результат должен превзойти текущую реализацию JDK.
PS: Пожалуйста, спаси меня "корень всей злой речи"
Уточнение прецедента:
Представьте, что у меня есть класс с ThreadLocal:
class A {
ThreadLocal<Counter> 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()
}
Ответы
Ответ 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 и добавить свои собственные поля; однако только те потоки, которые вы явно создаете из этого класса, будут иметь эти поля, поэтому вам все равно придется "откидываться" на использование локального потока.
Реальный вопрос: "почему?", как в "Почему локальный поток недостаточен для ваших требований?"