是否可以挂钩线程创建?
Is it possible to hook thread creation?
我正在制作一个插件系统,我需要查看插件何时调用 Thread.start()
有没有类似于 Runtime.getRuntime().addShutdownHook 但在线程启动时挂钩的方法?
您可以枚举线程,但无法同步挂钩创建。
嗯。也许有办法。
如果当前上下文有 SecurityManager
,则检查当前线程是否允许访问新 Thread
的 ThreadGroup
正在建设中。
所以...
您可以(理论上)设置自定义 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.
我正在制作一个插件系统,我需要查看插件何时调用 Thread.start() 有没有类似于 Runtime.getRuntime().addShutdownHook 但在线程启动时挂钩的方法?
您可以枚举线程,但无法同步挂钩创建。
嗯。也许有办法。
如果当前上下文有 SecurityManager
,则检查当前线程是否允许访问新 Thread
的 ThreadGroup
正在建设中。
所以...
您可以(理论上)设置自定义 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.