在Java中,有没有一种方法可以记录*每个*线程的中断?

7
我希望能够记录每次调用Thread.interrupt()的时间,记录发出调用的线程(以及其当前堆栈),以及有关被中断的线程的标识信息。您能否提供一种方法来实现这一点?我搜索了相关信息,看到有人提到可以实现安全管理器。这是一种可以在运行时(例如在Applet或Web Start客户端中)完成的操作吗?还是需要对已安装的JVM进行工具化处理?或者,是否有更好的方法来实现这一点?

我可以想出几种不同复杂度和不同性能影响的方法来解决它。这是为了找到一个 bug 还是你想要在生产中使用? - Fredrik
这主要是我想要的一次性东西,但我知道我将会不时地需要使用它。如果它足够友好,我会将其留在生产代码中,但禁用作为远程调试(通过日志)的附加模式,在需要时使用。 - Eddie
5个回答

10
作为一个快速的hack,这比我想象中要容易得多。由于这是一个快速hack,我没有做一些像确保堆栈跟踪足够深度之前解除引用数组等事情。我在我的已签名Applet的构造函数中插入了以下内容:
log.info("Old security manager = " + System.getSecurityManager());
System.setSecurityManager(new SecurityManager() {
      @Override
      public void checkAccess(final Thread t) {
        StackTraceElement[] list = Thread.currentThread().getStackTrace();
        StackTraceElement element = list[3];
        if (element.getMethodName().equals("interrupt")) {
          log.info("CheckAccess to interrupt(Thread = " + t.getName() + ") - "
                   + element.getMethodName());
          dumpThreadStack(Thread.currentThread());
        }
        super.checkAccess(t);
      }
    });

dumpThreadStack方法如下:

public static void dumpThreadStack(final Thread thread) {
  StringBuilder builder = new StringBuilder('\n');
  try {
    for (StackTraceElement element : thread.getStackTrace()) {
      builder.append(element.toString()).append('\n');
    }
  } catch (SecurityException e) { /* ignore */ }
  log.info(builder.toString());
}

当然,我不会在生产代码中留下这样的代码,但它足以告诉我到底是哪个线程导致了我没有预期的interrupt()。也就是说,在有了这段代码之后,我可以获取每次调用Thread.interrupt()的堆栈转储。


1
请注意,在某些版本的Java中,SecurityManager还应该覆盖checkPermission方法(对于最宽松和最简单的选项,请让它们简单地返回),否则您可能会遇到安全错误,如“java.security.AccessControlException:access denied”。 - vazor

4

在尝试任何过于激进的操作之前,您考虑过使用Java调试 API吗?我认为在Thread.interrupt()上捕获MethodEntryEvents就可以了。

哎呀,那是旧接口,您还应该检查一下新的JVM工具接口


我对Java调试API有一些模糊的了解,但我不确定如何在我要使用它的应用程序中使用它。 - Eddie
我已经给了你几个链接;简短的回答是,你需要在JVM调用中添加一个标志,然后就可以从外部连接到JVM。 - Charlie Martin
很遗憾,您的建议对我没有帮助。新的JVM工具接口需要JNI,而旧的Java调试API不适用于Applets。然而,这些信息在将来可能会对其他事情有用。感谢您的指引。 - Eddie
你应该能够在小程序中获取JDPA;你需要获取插件控制相关内容。请参阅http://java.sun.com/j2se/1.4.2/docs/guide/jpda/conninv.html#Plugin。 - Charlie Martin

2
我建议使用AspectJ及其包装方法调用的功能。在interrupt()方法周围添加执行切入点应该有所帮助。
请注意,由于您想要拦截Java系统方法调用(而不是应用程序代码),因此上述方法可能不适用。这个线程似乎表明这是可能的,但请注意他创建了一个被编织的rt.jar文件。

你甚至可以在JVM库中封装方法? - Eddie
这是一个很好的观点,正如我打字时考虑的一样。肯定有各种不同的字节编织机制(编译时/运行时/类加载时),所以其中之一可能会起作用。 - Brian Agnew

1

正如其他人所说... 如果只是一次性的事情,JVMTI可能是最强硬的方法。然而,使用asm库和instrumentation API创建一个代理程序,在Thread.interrupt()调用之前插入对静态方法的调用(或者修改Thread.interrupt()方法以执行相同操作,我认为你可以这样做),也是一种有趣的方式。

两种方法都需要一些时间来学习,但是一旦掌握了它们,你就可以在未来用它们做各种有趣的事情 :-) 我现在没有什么好的代码片段可以粘贴在这里,但是如果你在谷歌上搜索ASM并查看JIP中ASM的创造性用法,我想你会找到灵感。

JIP: Java Interactive Profiler


我猜你指的是http://asm.ow2.org/?听起来非常有创意!我没有时间去调查,但这看起来像是将来对我有好处的东西。谢谢。 - Eddie
正确,抱歉没有包括URL。 - Fredrik

0

你也可以尝试使用JMX:

ManagementFactory.getThreadMXBean().getThreadInfo(aThreadID)

使用ThreadInfo对象,您可以记录:

  • 线程的堆栈跟踪
  • 一般有用的信息,如名称、状态等
  • 等等

编辑

使用getAllThreadIds()以获取活动线程ID列表:

long[] ids = ManagementFactory.getThreadMXBean().getAllThreadIds();

你的意思是如何捕获中断调用? - Fredrik
使用其他方法(JVM 工具、调试钩子、AOP 等)? - dfa

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接