IBM的Yao Qi,Raja Das和Zhi Da Luo描述了jucprofiler,这是一个alphaWorks工具,旨在使用Java 5中引入的java.util.concurrent类对多核应用程序进行概要分析。
1.简介
性能分析是应用程序开发过程的重要方面。 它通常由专家完成,其主要目标是提高给定平台上的代码性能。 当处理在多核平台上运行的并发/多线程应用程序时,此问题变得更加困难。 在这种情况下,不仅要担心代码性能,还要担心代码的可伸缩性。
随着Java 5 中java.util.concurrent (JUC)包的引入,Java语言中引入了一种新型的锁。 随着开发或微调更多应用程序以使其在多核系统上更好地运行,JUC软件包的使用正变得越来越流行。 但是,尽管可以通过JLM获得有关常规Java锁的详细竞争信息,但java.util.concurrent.locks包不存在等效的竞争信息。 Sun / Oracle没有提供带有JDK的工具,IBM和其他Java供应商都没有。 缺少JUC锁定配置文件工具是开发作为Multicore SDK一部分的锁定工具jucprofiler的动机。
2. jucprofiler的简要概述
使用JUC锁时,在以下两种情况下,线程将“停止”执行:
- 当线程A尝试获取JUC锁时,而另一个线程已获取了该锁,则线程A必须“停止”其执行,并等待直到该锁被释放或超时。
- 当线程A调用java.util.concurrent.locks.Condition的“等待” API之一时,线程A“停止”其执行,直到另一个线程通知它或超时。
我们将这两种情况分别标识为争用时间和等待时间 。
Jucprofiler的设计和实现可捕获这两种情况的时间使用情况。
2.1仪器
为了捕获JUC锁运行时数据,脱机检测了几个JUC类,并在JRE中将其替换为新类。 在首次使用jucprofiler之前,用户必须运行命令来生成PreInstrument.jar。 假设JRE不变,则该步骤只需执行一次。 (如果用户更改为另一个JRE,则必须删除PreInstrument.jar,然后重新运行此命令以再次生成PreInstrument.jar。)
![](https://i-blog.csdnimg.cn/blog_migrate/8511cbdd5cca6959ba40b592bfe8b439.png)
2.1.1。 争用时间
对于争用时间,jucprofiler记录java.util.concurrent.locks.AbstractQueuedSynchronizer和java.util.concurrent.locks.AbstractQueuedSynchronizer $ ConditionObject的分配,并为其分配唯一的ID。
类 | 方法 | 致电网站 |
java.util.concurrent.locks.LockSupport | 公园(对象); | 类AbstractQueuedSynchronizer中的parkAndCheckInterrupt() |
parkNanos(对象阻滞剂,长纳米) | doAcquireNanos(int arg,long nanosTimeout) 类AbstractQueuedSynchronizer中的doAcquireSharedNanos(int arg,long nanosTimeout) |
2.1.2。 等待时间
对于锁上的时间使用情况,当在不同位置调用这两种方法时,jucprofiler会捕获类java.util.concurrent.locks.LockSupport中调用park(blocker)和parkNanos(blocker,nanos)的时间使用情况:
类 | 方法 | 致电网站 |
java.util.concurrent.locks.LockSupport | 公园(对象); | 类AbstractQueuedSynchronizer中的parkAndCheckInterrupt()以外的其他方法 |
parkNanos(对象阻滞剂,长纳米) | 除了doAcquireNanos(int arg,long nanosTimeout)以外的其他方法 类AbstractQueuedSynchronizer中的doAcquireSharedNanos(int arg,long nanosTimeout) |
3.如何使用工具
在本节中,我们将使用一个实际应用程序来探索如何使用该工具在代码中查找问题。
3.1。 运行jucprofiler
使用以下命令:
$ java -Xbootclasspath/p:$JUCP/BCIRuntime.jar:$JUCP/PreInstrument.jar
-javaagent:$JUCP/BCIAgent.jar=callStackDepth=10:allocationStackDepth=0
:,msdk.idp=com.ibm.msdk.bciagent.JUCInstrumentDecisionProvider,:libPath=$JUCP:traceJUC=on
-cp .:derby.jar JavaDBDemo
jucprofiler可以用于JDK 6上的任何Java程序。假设jucprofiler安装在$ JUCP目录中。 jucprofiler完成运行程序后,将生成一个名为“ BCIAgent。***。bci.trace”的跟踪文件,其中“ ***”是该执行的唯一时间戳。
3.2。 得到结果
运行下面显示的命令以获取Juc分析结果。
$ java -Xmx1000m -jar $JUCP/BCITraceReader.jar {tracefile} {resultOutputFile}
哪里:
- {tracefile}是包含跟踪文件(例如BCIAgent。***。bci.trace)的文件或目录的完整路径。
- {resultOutputFile}是一个可选参数,用于设置存储分析结果的文件。 如果省略此选项,分析结果将打印到控制台。
注意 :由于跟踪文件的后分析过程可能会遭受一些内存开销,因此最好通过-Xmx Java选项来增加进程堆大小。 在我们的实验中,分析160M跟踪可能会消耗800M内存。
3.3。 了解分析结果
如下图所示,纯文本输出包含不同种类的信息,例如锁名,锁争用计数和时间,锁保持时间和计数,锁分配堆栈跟踪,每个锁争用的持续时间和堆栈跟踪等。结果可以帮助用户找出程序中的JUC锁争用瓶颈。
在“ LEGEND”部分之前,性能分析结果报告首先总结程序中的所有JUC锁争用,按锁争用计数然后是争用时间的降序排列。 每个摘要行项目都属于两种不同类型之一,单个JUC锁的“ AQS”和ConcurrentHashMap的“ CHM”。 由于ConcurrentHashMap被分为几个用于元素存储的段,并且每个段都由不同的JUC锁保护,因此从锁的角度来看,ConcurrentHashMap可以看作是JUC锁的集合。 例如,下面的“ CHM @ 8”的竞争计数为276,竞争时间为3,945,7000,这意味着“ CHM @ 8”中所有段锁定的总竞争计数为276,而所有段的总竞争时间锁定“ CHM @ 8”为3,945,7000纳秒。 锁的这种分组有助于程序员识别哪个ConcurrentHashMap对象具有最严重的JUC锁争用。 相反,JUC锁“ AQS @ 1790”不属于任何ConcurrentHashMap对象,并且该锁在程序中显式使用。
注意:由于在示例跟踪中未启用锁定保持事件,因此将0放置在HOLD-COUNT和HOLD-TIME列中。
在“ LEGEND”部分之后,分析结果报告每个JUC锁争用的详细信息。 在以下结果片段中,对于ConcurrentHashMap,“ CHM @ 8”锁争用发生在两个段锁中(锁[AQS @ 135]和“锁[AQS @ 146]”)。 对于“ Lock [AQS @ 135]”,它在一个地方竞争,显示争用计数,争用时间和争用的完整堆栈追溯。 “锁定[AQS @ 146]”也是如此。 这些详细信息可帮助程序员在程序中定位锁争用,并清楚地了解ConcurrentHashMap的哪些部分竞争最激烈。
Multicore Software Development Toolkit Version_2.1
j.u.c Lock Profiler Report
NAME CONTD-COUNT CONTD-TIME HOLD-COUNT HOLD-TIME
CHM@8 276 39457000 0 0
AQS@1790 36 4029000 0 0
AQS@131 17 630000 0 0
=================================================================================================
LEGEND:
NAME : Name of juc lock(AQS) or ConcurrentHashMap(CHM), format: <Type>@<Idgt;
CONTD-COUNT : Total count of lock contention
CONTD-TIME : Total time of lock contention in nanosecond
HOLD-COUNT : Total count of lock hold
HOLD-TIME : Total time of lock hold in nanosecond
==================================================================================================
ConcurrentHashMap [CHM@8]:
-----------------------------------------------------------------------------------------------------------
Lock [AQS@135]:
-----------------------------------------------------------------------------------------------------------
Lock Contention 1
CONTD-COUNT: 25
CONTD-TIME: 10827000
Call Stack:
java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:758)
java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:789)
java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1125)
java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLock.java:197)
java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:273)
java.util.concurrent.ConcurrentHashMap$Segment.remove(ConcurrentHashMap.java:530)
java.util.concurrent.ConcurrentHashMap.remove(ConcurrentHashMap.java:934)
org.apache.derby.impl.services.locks.ConcurrentLockSet.unlock(ConcurrentLockSet.java:740)
org.apache.derby.impl.services.locks.ConcurrentLockSet.unlockReference(ConcurrentLockSet.java:784)
org.apache.derby.impl.services.locks.LockSpace.unlockReference(LockSpace.java:275)
Lock [AQS@146]:
-----------------------------------------------------------------------------------------------------------
Lock Contention 1
CONTD-COUNT: 22
CONTD-TIME: 2009000
Call Stack:
java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:758)
java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:789)
java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1125)
java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLock.java:197)
java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:273)
java.util.concurrent.ConcurrentHashMap$Segment.remove(ConcurrentHashMap.java:530)
java.util.concurrent.ConcurrentHashMap.remove(ConcurrentHashMap.java:934)
org.apache.derby.impl.services.locks.ConcurrentLockSet.unlock(ConcurrentLockSet.java:740)
org.apache.derby.impl.services.locks.ConcurrentLockSet.unlockReference(ConcurrentLockSet.java:784)
org.apache.derby.impl.services.locks.LockSpace.unlockReference(LockSpace.java:275)
3.4。 在可视化分析器中打开跟踪文件
有一些在Eclipse中开发并作为多核SDK(称为Visual Analyzer)的一部分提供的视图,其中显示了具有表和图形的jucprofiler跟踪文件,目前,jucprofiler的视图有两种,一种称为“ JUC”。统计信息”视图,另一个是“ JUC同步视图”。
“ JUC统计信息”视图如下所示。 右侧的两列是“争用时间”和“争用计数”。 “分配堆栈”列与JUC锁的分配调用站点有关。
![](https://i-blog.csdnimg.cn/blog_migrate/037b18bd1d8140b32dd1b4dbe42066a2.gif)
“ JUC同步”视图如下所示。 第一个通道是时间,指示何时发生此锁定争用。 第二个通道是Thread,指示在哪个线程中发生锁争用。 第三个通道是Monitor,指示已满足哪个JUC锁定。 最后一个通道是“方法”,指示锁的竞争位置。
![](https://i-blog.csdnimg.cn/blog_migrate/f6e5ff796526a0aa684b4d21a22fe21d.png)
3.5。 在线控制
在执行期间,jucprofiler将创建一个监听2009端口的ControlServer。用户可以使用ControlClient连接到该端口并控制jucprofiler的行为,例如可以实时打开和关闭跟踪:
$ java -cp BCIRuntime.jar
com.ibm.msdk.bciruntime.control.ControlClient HOST -m [b|i] –b
START -e END
哪里:
主机:ControlClient想要连接的主机名,默认为localhost。
-m [b | i]:ControlClient的模式-b表示批处理模式,而i表示交互模式。 默认为交互模式。
-b START:如果将模式设置为批处理模式,则START是您要开始分析的时间(秒)。
-e END:END是您要停止分析的持续时间(秒)。
3.5.1。 互动模式
提供了一个简单的外壳,用户可以键入juc.on和juc.off命令来打开和关闭jucprofiler。 例如,对于java -cp BCIRuntime.jar com.ibm.msdk.bciruntime.control.ControlClient,ControlClient将连接到本地主机,并打开一个外壳来控制jucprofiler。
$ java -cp BCIRuntime.jar com.ibm.msdk.bciruntime.control.ControlClient
jucprofiler control> juc.on
juc.on
jucprofiler t control> start
start
jucprofiler control> stop
stop
jucprofiler control> juc.off
juc.off
jmit control> quit
quit
$ java -cp BCIRuntime.jar com.ibm.msdk.bciruntime.control.ControlClient localhost -m b -b 2 -e 10
Start tracing in 2 seconds
Start tracing
Stop tracing in 10 seconds
Stop tracing
quit
3.5.2。 批处理模式
jucprofiler还支持批处理模式。 例如, java -cp BCIRuntime.jar com.ibm.msdk.bciruntime.control.ControlClient mtrat-test.dyndns.org -mb -b 10 -e 10表示ControlClient将连接到机器mtrat-test.dyndns.org, 10秒钟后启动Profiler,10秒钟后停止分析。
4。结论
随着多核的普及,将开发越来越多的并发/多线程Java应用程序。 我们需要更好的工具来分析此类并发应用程序。 本文中描述的jucprofiler填补了Java分析工具中的关键空白之一。