2

我们在单个物理主机上运行多个应用程序实例(每个 tomcat 服务器一个)。应用程序做体面的日志记录。最近我们观察到一些应用程序变慢或挂起,需要重新启动。在线程转储中,发现所有线程在日志语句上都被 BLOCKED,等待锁定println对象。和其他一些对象已经锁定println。但我不明白为什么其他线程没有释放println对象上的锁?我粘贴了一些线程转储快照:

阻塞的线程转储:

java.lang.Thread.State: BLOCKED (on object monitor)
    at java.io.PrintStream.println(PrintStream.java:755)
    - waiting to lock <0x00000007830097e0> (a java.io.PrintStream)
    at org.apache.tomcat.util.log.SystemLogHandler.println(SystemLogHandler.java:238)
    at com.webaroo.smsnew.common.SMSUtils.log(SMSUtils.java:167)
    at com.webaroo.smsnew.common.SMSUtils.log(SMSUtils.java:113)

记录 println 的线程的线程转储。

java.lang.Thread.State: BLOCKED (on object monitor)
    at java.nio.CharBuffer.wrap(CharBuffer.java:350)
    at sun.nio.cs.StreamEncoder.implWrite(StreamEncoder.java:246)
    at sun.nio.cs.StreamEncoder.write(StreamEncoder.java:106)
    - locked <0x00000007830098f0> (a java.io.OutputStreamWriter)
    at java.io.OutputStreamWriter.write(OutputStreamWriter.java:190)
    at java.io.BufferedWriter.flushBuffer(BufferedWriter.java:111)
    - locked <0x00000007830098f0> (a java.io.OutputStreamWriter)
    at java.io.PrintStream.write(PrintStream.java:476)
    - locked <0x00000007830097e0> (a java.io.PrintStream)
    at java.io.PrintStream.print(PrintStream.java:619)
    at java.io.PrintStream.println(PrintStream.java:756)
    - locked <0x00000007830097e0> (a java.io.PrintStream)
    at org.apache.tomcat.util.log.SystemLogHandler.println(SystemLogHandler.java:238)
4

1 回答 1

2

在 log 语句中发现所有线程都被 BLOCKED,等待 println 对象上的锁定。并且其他一些对象已经锁定了 println。

PrintStream和都是同步类System.outSystem.err任何println(...)方法在执行打印之前都会锁定,这样多个线程就不会得到重叠的输出行。

仅仅因为线程转储显示在该位置阻塞的线程并不意味着它已挂起。这可能只是意味着它是您的应用程序中最慢的部分。更多线程转储将表明其他线程正在进入println()但也被阻塞在那里。如果那里有许多线程被阻塞,那么输出 IO(可能到控制台)就是减慢应用程序的原因。您应该减少日志方法的数量或减少每条消息中的信息量。如果这没有帮助,那么您将不得不考虑其他输出机制。

如果您需要输出,则每个线程都可以写入自己的BufferedWriter包装 aFileWriter例如。或者,您可以让一个线程执行实际输出,而所有其他线程将它们的消息添加到 aBlockingQueue并且一个写入器将消息出列,然后将其写入BufferedWriter不锁定并缓冲其 I/O 的写入器。

private final BlockingQueue<String> messageQueue
      = new ArrayBlockingQueue<String>();
...
// add a message to the queue
messageQueue.add("some log output here: " + someValue);
...
// writer thread
private class LogThread implements Runnable {
    public void run() {
       BufferedWriter writer =
            new BufferedWriter(new FileWriter("/var/log/some_log_file.txt"));
       try {
          while (!Thread.currentThread().isInterrupted()) {
             String msg = messageQueue.take();
             writer.write(msg);
          }
       } finally {
          writer.close();
       }
    }
}

但是,您可能会发现,即使您有一个线程通过缓冲流写入,您也超过了硬盘的 IO 带宽。您可以在此时尝试 gziped 流,但通常需要重新评估输出。你能减少输出线的数量吗?您能否在内存中保留某种计数器并经常转储它。如果您真的需要日志输出,那么您可能不得不考虑通过移动到 SSD 来提高驱动器的速度。

于 2013-11-03T19:54:05.980 回答