本文是LZ在网上看到的文章,LZ觉得写得非常好,很有实际意义,可以作为以后的一个调优指导,故将文章搬到这里,如有侵权,欢迎与我联系,原文地址:https://www.ibm.com/developerworks/cn/java/j-lo-performance-tuning-practice/index.html
Java应用性能优化是一个老生常谈的话题,典型的性能问题如页面相应慢、接口超时、服务器负载高、并发数低、数据库频繁死锁等。尤其是“糙快猛”的互联网开发模型大行其道的今天,随着系统访问量的日益增加和代码的臃肿,各种性能问题开始纷至沓来。Java应用性能的瓶颈点非常多,比如磁盘、内存、网络I/O等系统因素,Java代码,JVM GC,数据库,缓存等。
笔者根据个人经验,将Java性能优化分为4个层级:应用层,数据库层,框架层,JVM层。每层的优化程度逐级增加,设计的知识和解决的方式也尽不相同。比如应用层需要理解代码逻辑,通过Java线程栈定位有问题代码行等;数据库层面需要分析SQL,定位死锁等;框架层需要懂源代码,理解框架机制;JVM层需要对GC的类型和的工作机制有深入了解,对各种JVM参数了然于胸。
围绕Java性能优化,有两种基本的分析方法:现场分析法和事后分析法。现场分析法通过保留现场,再采用诊断工具分析定位。现场分析对线上影响比较大,部分场景不太合适。事后分析法需要尽可能多的收集现场数据,然后立即恢复服务,同时针对收集的现场数据进行事后分析和复现。
下面我们从性能诊断工具出发,分享一些工作中遇到的真实案例。
性能诊断工具
性能诊断一种是针对已经确定有性能问题的系统和代码进行诊断,还有一种是对预上线系统提前性能测试,确定性能是否符合上线要求。本文主要针对前者,后者可以使用各种性能压测工具进行压测,比如JMeter等,但这不在本文讨论范围。针对Java应用,性能诊断工具主要分为两层:OS层面和Java应用层面。
OS诊断
OS诊断主要关注的是CPU,Memory,I/O三个方面。
CPU诊断
对于CPU主要关注平均负载(Load Average),CPU使用率,上下文切换次数(Context Switch)。
通过top命令可以查看系统平均负载和CPU使用率,输入o之后输入字段可以进行倒序排序
top -pid
1 | ➜ ~ top -pid 1492 |
平均负载 Load AVG
有3列数字2.19, 2.15, 2.12,分别表示过去1分钟,5分钟,15分钟机器的负载。按照经验,若数值小于0.7*CPU个数,则系统工作正常。这台机器是2个CPU,15分钟内平均负载为2左右,这个其实还好,如果是四五倍的话那这个就负载比较高了,这就需要定位具体的原因了。
通过vmstat命令可以查看CPU的上下文切换次数,上下文切换场景主要有如下几种:
- 时间片用完,CPU正常调度下一个任务。
- 被其他优先级更高的任务抢占。
- 执行任务碰到I/O阻塞,挂起当前任务,切换到下一个任务。
- 用户代码主动挂起当前任务让出CPU。
- 多任务抢占资源,由于没有抢到被挂起。
- 硬件中断。
Java线程上下文切换主要来自共享资源的竞争,一般单个对象加锁很少成为系统瓶颈,除非锁粒度过大。但在一个访问频度高,对多个对象连续加锁的代码块中就容易出现大量的上下文切换,成为系统瓶颈。比如在我们的系统中就出现过log4j1.x在较大并发下大量打印日志,出现频繁上下文切换,大量线程阻塞,导致系统吞吐量大降的情况,其代码如下,升级到log4j2.x才解决这个问题。
1 | for(Category c = this; c != null; c=c.parent) { |
这也给我们平时写代码的时候提了个醒,synchronized尽量放在for循环外面。
Memory
从操作系统角度,内存关注应用进程是否足够,可以使用free -m命令查看内存的使用情况。通过top命令可以查看进程使用的虚拟内存VIRT和物理内存RES,根据公式RES = VIRE + SWAP可以推算出具体应用使用的交换分区(Swap)情况,使用交换分区过大会影响Java应用性能,可以将Swap值尽可能调小。因为对于Java应用来说,占用太多交换分区可能会影响性能,毕竟磁盘的性能比内存慢太多。
I/O
I/O包括磁盘I/O和网络I/O,一般情况下磁盘更容易出现I/O瓶颈。通过iostat可以查看磁盘的读写情况,通过CPU的I/O wait可以看出磁盘I/O是否正常。如果磁盘I/O一直处于很高的状态,说明磁盘太慢或者故障,成为了性能瓶颈,需要进行应用优化或者更换磁盘。
Java应用诊断工具
应用代码诊断
应用代码性能问题是相对好解决的一类性能问题。通过一些应用层面监控报警,如果确定有问题的功能和代码,直接通过代码就能定位;或者通过top + jstack 就可以定位,找出有问题的线程栈,定位到问题线程的代码上,也可以发现问题。对于更复杂,逻辑更多的代码段,通过Stopwatch打印性能日志往往也可以定位大多数应用代码性能问题。
常用Java应用诊断包括线程、堆栈、GC方面的诊断。
jstack
jstack命令通常配合top使用,通过top -H -p <pid>
定位为Java进程和线程,再利用jstack -l <pid>
导出线程栈。由于线程栈是瞬态的,因此需要多次dump,一般3次dump,每次间隔5秒。将top定位的Java线程pid转换成16进制,得到Java线程栈中的nid,可以站到对应的问题线程栈。