【问题标题】:Where to look for synchronized contention evidence in java?在哪里寻找 java 中的同步争用证据?
【发布时间】:2009-08-08 14:28:37
【问题描述】:

我们的 Tomcat Web 应用程序在被数百名用户使用时感觉很慢。服务器位于托管公司,他们的报告没有显示带宽或 CPU 工作负载有任何问题,所以我怀疑速度变慢的原因可能是因为我们在同步调用下封装的一些遗留代码的争用,因为它是更简单的路径。

我在开发环境中进行了一些人工测试,使用 ThreadLocal 解决方案更改同步调用,它变得更快,但我知道我的老板会要求我提供一些证据,证明它在生产中也会更快。

我如何确定线程争用是否是我的应用程序中的问题?

【问题讨论】:

    标签: java performance multithreading synchronization contention


    【解决方案1】:

    我认为最近的 Java 6 JDK 附带的 visualVM 工具的 thread details 视图将能够为您的理论提供确凿的证据(或反对)。它为每个线程显示一个饼图,显示它在运行、睡眠、等待和在监视器中花费了多少时间。最后一个(显示为红色)是您感兴趣的:

    【讨论】:

      【解决方案2】:

      如果您有一个您认为更快的修改版本,请使用一些负载测试器(例如JMeter)来测试这两个版本。如果存在显着差异,您将有结果来证明这一点。

      【讨论】:

      • 谢谢,但我们没有与生产环境相同的适当集成环境来运行此类测试。在开发中运行 JMeter 测试将与我已经完成的测试相同。
      【解决方案3】:

      您可以随意使用一堆open source java profilers,以及其他可能要花钱的东西,例如YourKit。您应该使用现有代码和增强代码运行测试。使用 ThreadLocals 应该可以减少一般的争用,但请考虑在开始优化之前进行基准测试也是好的。

      另一个无需设置任何分析器即可完成的非常简单的测试是在应用程序看起来很慢时进行一些线程转储(ctrl-break 或 kill -QUIT)。在很短的时间内发现一些线程在相似或相同的监视器上等待可能会非常清楚地指出慢点。您可以使用tools like TDA,一个Java 线程转储分析器来帮助您梳理线程转储。

      同样,在开始优化之前完成这项工作是个好主意。这是因为,尽管优化可能会在某些明显的地方产生影响,但实际的用户行为可能会触发开发人员没有考虑的路径,而这些可能会成为真正的问题区域。

      【讨论】:

      • 我之前使用 Eclipse TPTP 和 Netbeans 分析了应用程序的某些部分,但是分析整个应用程序变得非常缓慢,所以我从未尝试在进行负载测试时进行分析,因为这需要几个小时,如果它不崩溃。
      • 是的,尝试将大型应用程序置于分析器下可能会很费力。 Michael Borgwardt 建议的工具看起来很有前景。
      【解决方案4】:
      jstack PID
      

      将打印出带有进程 id PID 的 JVM 状态列表,以及线程状态信息。

      样本输出(摘录):

      "AWT-XAWT" daemon prio=10 tid=0x0000000000e5f800 nid=0x476d runnable [0x00007f1a75616000..0x00007f1a75616bf0]
         java.lang.Thread.State: RUNNABLE
          at sun.awt.X11.XToolkit.waitForEvents(Native Method)
          at sun.awt.X11.XToolkit.run(XToolkit.java:543)
          at sun.awt.X11.XToolkit.run(XToolkit.java:518)
          at java.lang.Thread.run(Thread.java:636)
      
      "Java2D Disposer" daemon prio=10 tid=0x0000000000d8b800 nid=0x476c in Object.wait() [0x00007f1a759df000..0x00007f1a759dfc70]
         java.lang.Thread.State: WAITING (on object monitor)
          at java.lang.Object.wait(Native Method)
          - waiting on <0x00007f1a82e2c3f8> (a java.lang.ref.ReferenceQueue$Lock)
          at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:133)
          - locked <0x00007f1a82e2c3f8> (a java.lang.ref.ReferenceQueue$Lock)
          at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:149)
          at sun.java2d.Disposer.run(Disposer.java:143)
          at java.lang.Thread.run(Thread.java:636)
      

      我还会尝试隔离争用的资源。例如如果遗留库被锁定以同步对数据库的写入,那么您可能会最小化写入。

      【讨论】:

        【解决方案5】:

        您的分析听起来很合理。你能附上例如将visualvm(在JDK中)添加到进程中,以便您可以查看时间花费在哪里?

        【讨论】:

          【解决方案6】:

          我可以像这样在我们的同步调用中添加日志记录

          //...
          long t0 = System.currentTimeMillis();
          synchronized(lockObj){
              logger.info("T sync :" + (t0 - System.currentTimeMillis()));
              //...
          }
          

          但这感觉又便宜又脏。

          【讨论】:

          • 如果你想得到有效数字,你应该使用 nanoTime(),而不是 currentTimeMillis()。
          • 请注意,这种日志记录>>可能
          猜你喜欢
          • 2019-05-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-10-21
          • 2015-01-16
          • 1970-01-01
          • 2013-03-10
          • 1970-01-01
          相关资源
          最近更新 更多