是否可以挂钩线程创建?

Is it possible to hook thread creation?

我正在制作一个插件系统,我需要查看插件何时调用 Thread.start() 有没有类似于 Runtime.getRuntime().addShutdownHook 但在线程启动时挂钩的方法?

您可以枚举线程,但无法同步挂钩创建。

嗯。也许有办法。

如果当前上下文有 SecurityManager,则检查当前线程是否允许访问新 ThreadThreadGroup正在建设中。

所以...

您可以(理论上)设置自定义 SecurityManager 并使用线程组访问检查作为“挂钩”创建新 Thread 的方法。但这只会告诉您正在创建一个线程,而不是那个线程实际上是什么。所以这种方法可能会或可能不会满足您的需求。

并且无法挂接 Thread.start() 调用。


另一种方法是扩展 Thread 并在创建线程或在您的子 class.

中启动时实现挂钩

您可以将调试器附加到您的 JVM 并将断点设置为 Thread.start()。或者甚至使用 JVMTI 等以编程方式进行。但是,应用程序 (AFAIK) 不可能针对其自己的 JVM1 使用 JVMTI ...所以这不是传统的“挂钩”。

最后,总是有下载 OpenJDK、修改 Thread class、构建自定义 JVM 并安装它的选项。这对于做一些实验来说可能没问题,但对于生产应用程序来说这将是一件疯狂的事情。 (您不想因为维护 OpenJDK 的永久分支而给自己……或您的客户……增加负担。)


1 - 即使在某些情况下可能,Thread class 也会带来额外的困难。例如,考虑像字节码重写这样的事情必须在 classloader 加载 class 之前完成。但是 Thread class 必须在 JVM 引导期间加载......并且在加载任何应用程序代码之前。

不是正常的方式,但请记住 Thread.start 是 public 并且不是最终的,因此您可以创建线程的子class 来在开始之前执行您的自定义逻辑. 但这显然不足以解决这个问题。

可能有效的黑魔法解决方案是从 运行time 中删除现有的 Thread class(这意味着修改您的 JRE/JDK)并替换它与您的“丰富”版本。只要新版本做与“正常”版本相同的事情(注册本机、调用本机代码)就应该没问题。

另一个黑魔法替代方法是实际查看 Thread.start0 调用的本机代码并修改它。但这意味着要再次使用您的 运行 时间环境。

最终想法是让应用程序始终运行 处于类似于调试模式的状态,并在到达断点时执行某些操作。这是可行的,因为这是 IDE 所做的。虽然我们刚刚转向双流程设计及其需要的内容。

我也想知道 Java instrumentation package 是否会在这里提供某种替代方案。

字节人

您可以使用 Byteman 将您自己的代码注入 thread.start() 方法。

事实上,在他们的网站上使用 Byteman 和 JVM classes 的第一个示例展示了如何在线程启动时打印到控制台。

教程中的 Byteman 脚本示例:

RULE trace thread start

CLASS java.lang.Thread

METHOD start()

IF true

DO traceln("*** start for thread: "+ [=10=].getName())

ENDRULE

有关更多实施细节,请参阅 https://developer.jboss.org/docs/DOC-17213#how_do_i_inject_code_into_jvm_classes


字节好友

如果 Byteman 不是你的菜,还有另一个名为 ByteBuddy which can be used to create a Java Agent 的库可以拦截 Thread class.

中的一个方法
public class ThreadMonitor {
  @RuntimeType
  public static Object intercept(@Origin Method method, 
                                 @SuperCall Callable<?> callable) {
    System.out.println("A thread start method called");
      return callable.call(); //Calling the original start method.
  }
}

public class ThreadMonitorAgent {
  public static void premain(String arguments, 
                             Instrumentation instrumentation) {
    new AgentBuilder.Default()
      .type(ElementMatchers.nameEndsWith("start"))
      .transform((builder, type, classLoader, module) -> 
          builder.method(ElementMatchers.any())
                 .intercept(MethodDelegation.to(ThreadMonitor.class))
      ).installOn(instrumentation);
  }
}

示例代码改编自 ByteBuddy github readme.