1. 导言
程序的性能分析是应用程序开发过程中的一个重要方面。这个工作一般是由一些专业人员来完成的,他们的目标是在一个特定的平台上,提高代码的性能。当程序是运行在多核平台的多线程或者并行程序的时候,提高性能这个问题就变得更加困难了。因为在这样的情况下,不仅需要考虑代码的性能,还需要考虑代码的可伸缩性。
随着Java 5中引入了java.util.concurrent (JUC)包,在Java语言中出现了一种新的锁。JUC包使用得越来越普遍,因为更多的应用程序需要为了多核系统而开发或仔细地调优。虽然JLM可以找到传统的Java锁的详细的竞争信息,但是却没有同样的工具能够找到java.util.concurrent.locks包的锁竞争信息。 Sun/Oracle、IBM,还有其他Java厂商也都没有这样的工具。缺乏对JUC锁的剖析工具正是我们开发这个锁工具,jucprofiler(Multicore SDK的一部分)的动机。
2. jucprofiler概览
当在程序中使用JUC锁的时候,线程会在下面两种情况下“停止”执行:
当线程A试图去获得一个JUC锁,但这个锁却已经被另外一个线程获得,那么线程A不得不“停止”,直到这个锁被释放或者超时。
当线程A调用了java.util.concurrent.locks.Condition的任意一个“等待”的API,线程A会停止执行,直到另外一个线程通知它或者超时。
我们分别把这两种情况称作“锁竞争时间”和“等待时间”。
jucprofiler就是为了捕获以上两种情况的时间开销而设计和实现的。
2.1. 代码修改(Instruments)
为了获取JUC锁的运行时数据,需要提前修改一些JUC类,然后替换掉JRE中相应的类。在首次使用jucprofiler之前,用户需要运行命令去生成PreInstrument.jar。假设JRE没有改变的话,这个步骤只需要做一次。(如果用户改变了JRE,那么用户需要自己删除 PreInstrument.jar,然后重新运行这个命令,来再次生成PreInstrument.jar)。
2.1.1. 锁竞争时间开销
对于锁竞争时间开销,jucprofiler记录了申请类 java.util.concurrent.locks.AbstractQueuedSynchronizer 和类java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject的实例,并且给这些实例分配唯一的标识。
类 | 方法 | 调用位置 |
java.util.concurrent.locks.LockSupport | park (Object); | 类AbstractQueuedSynchronizer中的方法parkAndCheckInterrupt() |
parkNanos(Object blocker, long nanos) | 类AbstractQueuedSynchronizer中的方法doAcquireNanos(int arg, long nanosTimeout)与doAcquireSharedNanos(int arg, long nanosTimeout) |
2.1.2. 锁等待时间开销
对于锁等待时间开销,jucprofiler获取了在不同的位置调用类java.util.concurrent.locks.LockSupport的方法park(blocker)与parkNanos(blocker, nanos)的时间开销:
类 | 方法 | 调用位置 |
java.util.concurrent.locks.LockSupport | park (Object); | 类 AbstractQueuedSynchronizer除parkAndCheckInterrupt()以外的方法 |
parkNanos(Object blocker, long nanos) | 类AbstractQueuedSynchronizer除doAcquireNanos(int arg, long nanosTimeout)与doAcquireSharedNanos(int arg, long nanosTimeout) 以外的方法 |