【问题标题】:How can I modify a java.lang class on the fly?如何即时修改 java.lang 类?
【发布时间】:2013-06-03 21:14:18
【问题描述】:

我正在寻找一种通过重写字节码并重新加载类来动态地将字段添加到线程的方法,但不确定它是否可能。欢迎任何指点。我找到了一些关于修改和加载类的信息,并且我知道 JRebel 可以无缝地热交换您的代码,但不确定相同的方法/工具是否适用于此。

这里的动机是探索一种理论上更好的替代线程本地对象的方法。如果该方法有效,我应该能够用注释替换本地线程,并且结果应该优于当前的 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()
}

【问题讨论】:

  • 您能否添加您目前拥有或正在从事的工作..代码以便我们可以更好地帮助您?
  • 对不起,这还处于初步研究阶段,目前没有可用的代码。我将编辑 Q 以澄清意图。

标签: java classloader dynamic-class-loaders dynamic-class-creation


【解决方案1】:

如果你想在运行时改变“类”的行为,你可以试试javassist。 API是here

【讨论】:

  • 我正在寻找一种方法来更改已经加载和分配的类。我可以找到足够的文档来支持在加载类时更改它,但不知道如何用我的新版本替换正在运行的线程。
【解决方案2】:

我见过动态重新加载 JAR 的自定义类加载解决方案 - 您为每个 JAR 文件定义一个 ClassLoader 并使用它从该 JAR 加载类;要重新加载整个 JAR,您只需“杀死”它的 ClassLoader 实例并创建另一个(在替换 JAR 文件之后)。

我认为不可能以这种方式调整 Java 的内部 Thread 类,因为您无法控制 System ClassLoader。一个可能的解决方案是创建一个 CustomThreadWeaver 类,该类将生成一个新类,该类使用您需要的变量扩展 Thread,并使用自定义 DynamicWeavedThreadClassLoader 加载它们。

祝你好运,成功后向我们展示你的怪物 ;-)

【讨论】:

    【解决方案3】:

    你试图做的事情是不可能的。

    由于您已经了解 ThreadLocal,因此您已经知道建议的解决方案是什么。

    或者,您可以对 Thread 进行子类化并添加自己的字段;但是,只有您为该类显式创建的那些线程才会具有这些字段,因此您仍然必须能够“退回”以使用本地线程。

    真正的问题是“为什么?”,例如“为什么本地线程不足以满足您的要求?”

    【讨论】:

    • 这是你真正的问题,不是我的:)。测量在 Thread 上具有字段和使用 ThreadLocal 之间的性能差异,这就是原因。为什么不可能?有一些产品,比如 JRebel,声称能够在运行时热交换你的代码,那么为什么不能热交换线程呢?
    • 但是答案有什么不正确的地方?顺便说一句,JRebel 通过在加载时更改类来工作,向任何需要修改的类添加一个附加字段。您可以自己更改 rt.jar 中的 Thread.class,因为您似乎不喜欢合理的建议。
    【解决方案4】:

    这似乎是一个使用正确工具来完成工作的问题。此处提出了类似的问题:Another Stack Overflow Question,Javaassist 字节码操作库是一个可能的解决方案。

    但如果没有进一步详细说明尝试这样做的原因,似乎真正的答案是使用正确的工具来完成这项工作。例如,使用 Groovy the ability to dynamically add methods to the language

    【讨论】:

    • 添加新方法不像添加新字段,所以这没有帮助。如果您知道如何使用 Javaassist 即时添加新字段,请告诉我。动机是一种智力锻炼,在这一点上是一种想法/想法。
    • 啊,我明白了。我自己没有使用过Javaassist,但我确实找到了声称能够添加字段的本教程:csg.is.titech.ac.jp/~chiba/javassist/tutorial/…
    【解决方案5】:

    您可以尝试创建一个使用 java.lang.instrument API 的 JVM 代理,更具体地说,使用“有助于检测已加载的类”的 retransform method,然后如上所述使用 Javassist(或 ASM)处理字节码。

    更多信息请关注java.lang.instrument API

    【讨论】:

    • 遗憾的是,文档声称这是不可能的:“重新定义可能会更改方法体、常量池和属性。重新定义不得添加、删除或重命名字段 .. 。”见here
    【解决方案6】:

    目前不可能在运行时重新定义一个类,这样重新定义就会产生新的方法或字段。这是由于在堆中扫描所有现有实例并转换它们 + 它们的引用 + 潜在的不安全字段偏移基础更新器(如 AtomicFieldUpdater)所涉及的复杂性。

    此限制可能会作为JEP-159 的一部分被取消,但正如在并发兴趣邮件组中所讨论的那样,这是一个巨大的影响变化,因此可能永远不会发生。

    使用 Javaassist/similar 将允许将类转换为具有新方法/字段的新类。此类可以由 ClassLoader 加载并在运行时使用,但它的定义不会替换现有实例。因此,不可能将此方法与代理结合使用来重新定义类,因为仪表重新定义受到限制:“重新定义可能会更改方法体、常量池和属性。重新定义不得添加、删除或重命名字段...”见here

    所以现在,不。

    【讨论】:

      【解决方案7】:

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

      【讨论】:

      • 请注意,这种方法也应该适用于通过创建动态生成的子类,例如javassist。
      • 这不是我想要的……我想动态添加字段。正如您所描述的那样,不是即时执行。
      • 但是对于您所描述的,您应该使用动态子类恕我直言。如果你有多个线程,其中一个使用 A,第二个使用 B。你真的想在同一个 Thread 类中为两者生成一个字段吗?另外,我看到的问题是 ThreadLocals 是实例的成员。因此,您应该为每个 A 实例生成一个对象,而不是为 A 类生成一个字段(好吧,我假设该示例只是为了演示问题)
      • 没关系,看到 ThreadLocal 需要存在于所有线程中,我想你是对的,你需要以某种方式在 Thread 类中走私你的字段。
      【解决方案8】:

      可能 使用仪器,可能还使用 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 代理的清单中。

      真实例子-使用javassist优化java 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()));
      

      【讨论】:

        猜你喜欢
        • 2018-12-27
        • 2014-05-25
        • 1970-01-01
        • 1970-01-01
        • 2021-08-06
        • 1970-01-01
        • 1970-01-01
        • 2017-10-25
        • 1970-01-01
        相关资源
        最近更新 更多