简体   繁体   English

如何动态修改java.lang类?

[英]How can I modify a java.lang class on the fly?

I'm looking for a way to add fields to an Thread on the fly by rewriting the byte code and reloading the class, not sure if it is at all possible. 我正在寻找一种通过重写字节代码并重新加载类来动态添加字段到线程的方法,不确定它是否完全可能。 Any pointers welcome. 欢迎任何指示。 I found some info on modifying and loading a class, and I know JRebel can seamlessly hot swap your code but not sure if the same approach/tools apply here. 我发现了一些关于修改和加载类的信息,我知道JRebel可以无缝地交换你的代码,但不确定这里是否适用相同的方法/工具。

The motivation here is exploring a theoretically better alternative to thread local objects. 这里的动机是探索理论上更好的替代线程本地对象。 Should the method work I should be able to replace thread local with an annotation and the result should outperform current JDK implementation. 如果方法有效,我应该能够用注释替换本地线程,结果应该优于当前的JDK实现。

PS: Please save me the "root of all evil speech" PS:请救我“所有邪恶言论的根源”

Clarifying use case: 澄清用例:

Imagine I have a class with a ThreadLocal: 想象一下,我有一个ThreadLocal类:


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

I'd like to replace that with an annotation: 我想用注释替换它:


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

But instead of the above code getting generated I'd like to mutate Thread such that Thread will now have an Acounter field and the actual code will be: 但是代替上面的代码生成我想改变Thread,这样Thread现在将有一个Acounter字段,实际的代码将是:


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

At present it is impossible to redefine a class at runtime such that the redefinition will result in new methods or fields. 目前,不可能在运行时重新定义类,以便重新定义将导致新的方法或字段。 This is due to the complexity involved in scanning the heap for all existing instances and transforming them + their references + potential Unsafe field offset base updaters (like AtomicFieldUpdater). 这是因为扫描所有现有实例的堆并转换它们及其引用+潜在的不安全字段偏移基本更新程序(如AtomicFieldUpdater)所涉及的复杂性。

This limitation may be lifted as part of the JEP-159 but as discussed on the concurrency-interest mailing group this is a big impact change so may never happen at all. 这个限制可以作为JEP-159的一部分解除,但正如并发利益邮件组所讨论的那样,这是一个很大的影响变化,所以可能永远不会发生。

Using Javaassist/similar will allow the transformation of a class to a new class with new methods/fields. 使用Javaassist / similar将允许使用新方法/字段将类转换为新类。 This class can be loaded by a ClassLoader and used at runtime, but it's definition will not replace existing instances. 此类可以由ClassLoader加载并在运行时使用,但它的定义不会替换现有实例。 So it will not be possible to use this method combined with an agent to redefine the class as an instrumentation redefinition is limited such that: "The redefinition may change method bodies, the constant pool and attributes. The redefinition must not add, remove or rename fields ..." see here . 因此,不可能将此方法与代理结合使用来重新定义类,因为检测重新定义受到限制,因此:“重新定义可能会更改方法主体,常量池和属性。重定义不得添加,删除或重命名田野......“看到这里

So for now, NO. 所以现在,没有。

If you would like to change the behaviour of "class" at runtime, you could try javassist . 如果您想在运行时更改“类”的行为,可以尝试使用javassist API is here API就在这里

I have seen custom class loading solution that dynamically reloaded JARs - you define one ClassLoader per JAR file and use it to load the classes from that JAR; 我见过动态重新加载JAR的自定义类加载解决方案 - 您为每个JAR文件定义一个ClassLoader并使用它来加载来自该JAR的类; to reload entire JAR you just "kill" its ClassLoader instance and create another one (after you replace the JAR file). 要重新加载整个JAR,只需“杀死”其ClassLoader实例并创建另一个实例(在替换JAR文件之后)。

I don't think it's possible to tweak Java's internal Thread class this way because you don't have control over System ClassLoader . 我认为不可能以这种方式调整Java的内部Thread类,因为您无法控制System ClassLoader A possible solution is to have a CustomThreadWeaver class that would generate a new class extending Thread with the variables you need and use a custom DynamicWeavedThreadClassLoader to load them. 一种可能的解决方案是使用CustomThreadWeaver类生成一个新类,该类使用您需要的变量扩展Thread ,并使用自定义DynamicWeavedThreadClassLoader来加载它们。

Good luck and show us your monster when you succeed ;-) 祝你好运,并在你成功时告诉我们你的怪物 ;-)

Possible using instrumentation, and possibly libraries like javassist to modify code on fly. 可能使用检测,可能还有像javassist这样的库来修改代码。 (however, adding and removing fields, methods or constructors are currently not possible) (但是,目前无法添加和删除字段,方法或构造函数)

//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));

Do not forget to add Can-Redefine-Classes: true to your java agent's manifest. 不要忘记添加Can-Redefine-Classes: true对于java代理的清单是Can-Redefine-Classes: true

Real example - optimizing java < 9 string.replace(CharSequence, CharSequence) using javassist: 真实的例子 - 使用javassist优化java <9 string.replace(CharSequence, CharSequence)

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()));

This seems to be a question of using the right tool for the job. 这似乎是使用正确的工具来完成工作的问题。 A similar question has been asked here: Another Stack Overflow Question and the Javaassist byte code manipulation library was a possible solution. 这里也提出了类似的问题: 另一个Stack Overflow问题和Javaassist字节代码操作库是一种可能的解决方案。

But without further detail into the reasons why this is being attempted, it seems like the real answer is to use the right tool for the job. 但是没有进一步详细说明为什么要尝试这样做的原因,似乎真正的答案是使用正确的工具来完成工作。 For example, with Groovy the ability to dynamically add methods to the language . 例如,使用Groovy 可以动态地向该语言添加方法

You could try creating a JVM Agent that makes use of the java.lang.instrument API and more specifically make use of the retransform method that " facilitates the instrumentation of already loaded classes" and then make use of Javassist (or ASM) as mentioned to deal with the bytecode. 您可以尝试创建一个使用java.lang.instrument API的JVM代理,更具体地说,使用“促进已加载类的检测”的重新转换方法 ,然后使用所提到的Javassist(或ASM)处理字节码。

More info on the java.lang.instrument API 有关java.lang.instrument API的更多信息

要做你想做的事,更简单的替代方法是使用Thread的子类,运行它,然后在该线程内执行你的例子中的代码(连同你的子类的currentThread()的强制转换)。

What you are attempting to do is not possible. 你试图做的是不可能的。

Since you already know about ThreadLocal, you already know what the suggested solution is. 由于您已经了解ThreadLocal,因此您已经知道建议的解决方案是什么。

Alternatively, you can sub-class Thread and add your own fields; 或者,您可以对Thread进行子类化并添加自己的字段; however, only those threads that you explicitly create of that class will have those fields, so you will still have to be able to "fall back" to using a thread local. 但是,只有那些明确创建该类的线程才会拥有这些字段,因此您仍然必须能够“回退”使用本地线程。

The real question is "why?", as in "why is a thread local insufficient for your requirements?" 真正的问题是“为什么?”,如“为什么本地线程不足以满足您的要求?”

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM