Java问题定位与深度调试技术(二)——通过Java线程堆栈进行性能瓶颈分析

252 阅读3分钟

一、基本原理分析

        改善性能的目标是用更少的资源做更多的事情。一条调用链路上,首先要找到性能最小的瓶颈,再解决次瓶颈才能逐步扩大性能。

1.1 系统瓶颈

  1. 如果系统逐渐增加压力,CPU的使用率仍然无法趋近100%,这说明这个程序的实现存在问题。
  2. 如果能在增压下达到100%也不说明程序一定是优秀的。也许可能是低效使用CPU。例如String字符串可以用StringBuffer.append()而不是直接生成。

1.2 好的程序应该怎么设计以及可能的问题

  1. 减少锁的粒度:比如synchronized关键字最好不要加在方法上,而是在方法里面的必须加速的代码片段。
  2. sleep的滥用:sleep只适合等待固定时间的场合,更多的用wait和notify。
  3. String+的滥用:用StringBuffer
  4. 不恰当的网络模型:在网络IO的场合,一定要使用消息发送队列和消息接收队列来进行异步IO。
  5. 不恰当的GC参数设置会导致严重的性能问题
  6. 避免线程数量不足
  7. 避免内存泄漏
  8. 避免过度的磁盘IO
  9. 资源竞争(如数据库连接池中连接不足,导致企图获取连接的大量线程被阻塞)造成很多线程由于无法获得资源而被挂起。一般资源池的设计都采用wait/notify机制,当无可用资源时,申请资源的线程执行wait挂起,直到有可用资源,才被再次notify。当资源不足时,从线程堆栈看,会有很多线程处在获取资源的wait方法上。
  10. 经常访问全局指标造成单一热点:例如访问concurrentHashMap的size
  11. 远程通信的对方处理缓慢。

1.3 发现性能瓶颈工具的优缺点

性能瓶颈是动态的,低负载下不是瓶颈的地方,在高负载下才可能会成为瓶颈。一些商业化软件Jprofiler、Optimizeit等,对于高压力下才能出现的瓶颈没办法给到有效的帮助,因为他们是通过Java Virtual Machine Tools Interface(JVM TI)进行性能分析,要依附于JVM,性能开销大,没办法一直开着。 在这种场合下用线程堆栈几乎是唯一手段。

1.4 工具总结

1.4.1 借助操作系统提供的cpu统计工具

例如top、prstat等,可以统计出每一个线程使用的cpu比例。对系统几乎没有任何影响。都是系统自带的,不需要搭建环境。

  • 适合分析的问题:可以分析哪些代码消耗CPU过多
  • 不适合分析的问题:
    • 由于锁的粒度不合理导致的性能瓶颈,虽然占有锁的线程,但是不一定消耗CPU。CPU利用率低。
    • 系统各种任务采用了同一个线程池。由于一个线程可能执行不同的任务,因此没有办法分离出该线程的具体执行代码到底是哪一次代码的耗时。

1.4.2 通过Java线程堆栈进行性能瓶颈分析

只有打印堆栈的时候对系统有一点影响。工具都是现成的,不需要搭建环境。

  • 适合分析的问题:多线程场合下,锁的粒度不合理以及资源竞争。

1.4.3 其他性能工具

runhprof、JProfiler、JBulider对系统消耗大,只能分析常见的代码块比较消耗CPU的情况。