目录
定位java应用
一、背景
在开发过程中,我们一定遇到过下面这些问题:
- 进程消耗CPU
- 内存利用率暴增
- 数据库连接被耗尽
- 各种OOM预防
- 线程死锁、锁争用、上下文切换太频繁
无论多复杂的系统运行在linux上,其实它就是一个进程,任何东西在操作系统层面都是以文件形式来存储的,进程也不例外,所以在操作系统的层面只需要关注进程和线程即可。
例如linux上部署一个tomcat程序产生了一个进程,这个进程的所有东西都在/proc/pid(进程号)/这个目录下,比如日志输出在哪里(fd目录),系统加载了哪些jar包,系统启动的时候是哪个java版本等这些问题都会在这个目录下找到答案。命令 ll /proc/2570/fd |grep socket可以查看该系统和哪些外部应用在交互,包括使用了哪些中间件服(redis、mq等),这些都可以通过socket的端口能够找到,因为应用底层的所有通信都是通过socket来完成的。
二、定位问题方法是什么
一个java应用,服务性能上我们只关注两点:吞吐量、系统响应性。
场景
例如,一个java应用,CPU的利用率不高但是系统的吞吐量和响应性却不理想。
首先,这种情况说明程序并没有忙于计算,可能问题出现在IO上。IO和CPU的利用率一般是呈反比的,CPU利用率高则IO不高,IO高则CPU利用率小。分析IO主要是三个维度:磁盘IO、驱动程序IO、内存换页率
然后,再检查一下网络带宽。如果CPU、内存、IO、网络都看完了并且CPU利用率不高、内存使用率、IO使用率、网络带宽都不高但是系统的性能还是上不去,那么程序一定是被阻塞了,可能是CPU在等待哪个锁,也可能是某个服务接口返回连接在等待等,包括上下文切换等。
分析
-
磁盘IO
类比一下磁盘IO、内存、CPU速度:
- 把CPU看做一个时钟周期,比如是1秒;
- 一级缓存就好像从桌子上拿起一张纸,用时大概3秒;
- 二级缓存就好像从书架上找一本书,用时大概14秒;
- 主存(内存)就好像去图书馆找一本书,用时大概4分钟;
- 磁盘寻道的时间就好像长达一年零三个月的环球旅行。
以上可以形象的看出它们之间的速度差距。
-
网络IO
某些环境下由于网络环境的不确定性尤其是互联网上的数据读写,网络操作的速度可能比本地IO要更慢。
-
CPU
CPU资源的争夺将导致性能问题,主要是锁竞争,锁竞争会导致上下文切换频繁带来严重的系统开销,表现就是内核态的CPU的使用率偏高。
-
内存
内存作为影响系统指标之一最大的可能是内存不足,但是对于java程序员来说我们只要关注下GC就可以。
-
连接
系统对外部依赖的中间件和接口,比如说数据库连接,如果数据量过大或者连接超时,都是比较消耗性能的。
-
java异常
java异常的捕获和处理是非常消耗资源的,如果程序高频率地在异常处理也会影响系统性能。
三、针对以上问题如何快速地做出诊断
1、CPU诊断
top命令,关注load average 显示最近1分钟、5分钟、15分钟之内系统的平均负载。平均负载定义:在特定的时间间隔内运行队列中即CPU上运行或者等待运行多少进程的平均进程数,比如系统有两个正在运行进程,三个可运行的进程那么系统的load就是5,load avera是一定时间内的load数量,所以CPU核数和load average的关系就好比高速公路上的多车道和车辆,如果车辆过多就显示拥挤,所以这个值越高说明系统的性能越差。另外,需要关注用户态的CPU和内核态的CPU的区别。
2、内存诊断
查看某个进程的物理内存和虚拟内存,使用命令
## pidstat -r -p pid 1 2 来定位
pidstat -r -p 17314 1 2
如果要查看某个进程文件的IO情况,可以通过pidstat加参数去查看。vmstat命令中swpd过高通常是物理内存不够用了,swap的消耗主要关注它的IO,如果IO发生比较频繁也会影响系统的性能问题。
3、网络IO
系统调用
trace命令可以用来跟踪进程的执行和系统的调用,通过 trace -T 参数 可以显示具体的进程系统调用的情况。例如网络的IO可以通过cat proc interrupts来查看,网络IO消耗需要关注网卡中断是否均衡分配到各个CPU。 java应用程序一般不会造成网络IO消耗严重的问题。
连接
应用往往还需要和外部的服务交互,比如说数据库、缓存,可以通过netstat命令来查看,就可以发现系统正在和哪些外部的应用交互,比如命令 netstat -anop | grep 3306可以发现系统有连接mysql的数据库,并且有多少个连接其中有时候会报错,has already more than 最大的连接数,这时候通常使用此命令就可能发现你是因为有比较多的连接数,当连接数非常多的时候,最大的可能是代码不停地操作数据库或者有慢sql导致的连接不释放,当你发现应用连接数大于正常连接数的时候系统的代码就有可能有问题。
tcp诊断
重点关注CLOSE-WAIT和TIME_WAIT。TIME_WAIT表示客户端主动关闭,CLOSE_WAIT表示被动关闭,如果客户端的并发量持续很高就会出现TIME_WAIT很高,有可能是客户端的连接连不上,可以通过netstat命令来查询定位。
4、磁盘诊断
一般使用命令dstat查看。
5、线程异常情况
关注两点:线程状态、线程数量。
线程数
应用设计的时候需要考虑资源的限制才能够避免应用在某些时候因为资源过度而被崩溃,线程数量的控制非常重要,无限制的创建线程最终导致其不可控,特别是隐藏在代码中的创建线程的方法,当系统的sy值过高时表示linux要花费更多的时间来进行线程的切换。java造成这种现象的主要原因是创建的线程比较多,这些线程都处于不断的阻塞、锁等待、IO等待和执行状态变化的过程中,这就产生了大量的上下文切换,java应用在创建线程时会操作jvm堆外的物理内存,太多的线程也会使用过多的物理内存导致了一定情况下我们会看到系统报如下错误:
Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
这个错误是告诉我们系统创建了太多的线程导致的。
如何查看线程数有以下几种命令:
cat /proc/pid/status
top -bH -d 3 -p pid
pstree -p pid |wc -l
pstack pid |head -l
【注意】当创建一个线程的时候,在jvm在内存创建一个对象的同时还会去创建一个操作系统线程,这个操作系统的线程用的是jvm外的内存,因此在系统内存一定的情况下给jvm内存越多,你能创建的线程越少,极端情况会出现unable to create new native thread,每个线程大概占用堆外内存1M(jdk1.5,不同jdk版本可能不同)。
线程状态
线程状态包含以下几种:
- Blocked
- Waiting
- Time_Waiting
- New Runnable
- Terminated
最要关注的是Blocked 、Waiting 和Time_Waiting特别是Blocked在获取不到cpu执行时间的时候,这样的系统性能就会下降。
6、cpu过载诊断
定位哪段代码导致cpu过载
- 步骤一:首先通过top或pidstat命令定位具体的进程号,比如9750
- 步骤二:top -p 9750命令后,然后按住shift加H键 开启显示详细的线程,通过观察定位具体的线程号。比如说说线程9751(子线程都是系统调用fork生产的子线程,都是线程9750的子线程、孙子线程等,所以进程中的线程号都比进程号大)。
- 步骤三:把进程号转换为十六进制,因为线程栈中的线程程序号是以16进制存储的
printf "%x\n" 9751
## 2617 9751的十六进制表示
- 步骤四:通过jstack命令和上面定位的线程号来分析这个线程正在做到事情,可以看到具体的代码。
jstack -l 9751 | grep -A 20 2617
7、分析OOM
java服务最常发生的就是OOM,一般原因有以下几种:
- 资源不够,内存分配的过小,而正常业务却使用了大量内存;
- 申请的资源太多,某一个对象被频繁的申请却没有被释放,内存不断泄露最终导致内存耗尽;
- 资源耗尽,某一个资源被频繁的申请,系统资源耗尽,例如:不断地创建线程,不断地发起网络连接。
定位:
- 确认你的内存本身是不是分配过小,可以通过jmap可以查看jvm内存使用情况,命令:jmap -heap 5924(pid);
- 找到最耗费内存的对象,也可以通过jmap命令jmap -histo:live 5924 | more;
- 确认资源是否耗尽,通过上面介绍的pstree和netstat来查看进程创建的线程数以及网络连接数等资源是否被耗尽。
其实内存分析最简单有效的方法还是通过内存的dump命令去导出内存栈,但是这个命令会进行FULL GC一次,导出的文件可以通过 MAT VisualVM来查看,其中shallow heap显示的是对象本身的大小,related heap表示的是本身的大小加引用的大小,另外如果定位代码正在做什么事情也可以通过上面CPU的方法来定位。
8、JVM GC问题
jvm有很多的工具和命令,这里我们主要看一下GC,GC会导致系统应用暂时的停顿,如果频繁的GC就会产生系统延迟响应,对于互联网面向用户实时交易延迟是不可以接受的,所以开启GC日志可以收集GC统计和信息方便观察。另外定位应用的停顿原因也可以通过jstat命令去收集它信息。
9、日志诊断
java的异常会导致性能问题,系统运行过程中所有的问题都会直接显示在日志中(前提是你的日志栈没有被吃掉),检查日志的异常、error等各种数据库异常等可以快速地了解系统的运行情况,一般这个可以通过很多成熟的工具比如监控系统,比如说awk命令都可以总结一下。