不知道你在多年的开发经验中,你的线上服务,有没有遇到过一下情况呢?
所有Java服务的线上问题从系统表象来看归结起来总共有四方面:CPU、内存、磁盘、网络。例如CPU使用率峰值突然飚高、内存溢出(泄露)、磁盘满了、网络流量异常、FullGC等等问题。
先说结论,大部分工程师也许没有全面的性能问题诊断机会,解决线上问题,也没有所谓的银弹。多实践,多学习底层原理,才能让你拥有更丰富的经验。
使用到的工具
系统MacOS14.3
Jprofiler14.0版本
CPU是系统重要的监控指标,能够分析系统的整体运行状况。监控指标一般包括运行队列、CPU使用率和上下文切换等。
top命令显示了各个进程CPU使用情况,一般CPU使用率从高到低排序展示输出。其中LoadAverage显示最近1分钟、5分钟和15分钟的系统平均负载,上图各值为0.11,0.08,0.05
关于线上的CPU问题,常见的问题有以下三种
往往都是业务逻辑问题导致的,比如死循环、频繁gc或者上下文切换过多。
线上问题排查,为了能够保持现场情况,一般我们直接登陆机器,使用命令行进行排查。
当然,为了方便你更好的理解,我也提供了一个简单的示例代码,如下所示。
publicclassCpuTests{publicstaticvoidbusyThread(){Threadthread=newThread(()->{while(true){}},"*busyThread");thread.start();}publicstaticvoidlockThread(Objectlock){Threadthread=newThread(()->{synchronized(lock){try{lock.wait();}catch(InterruptedExceptione){e.printStackTrace();}}},"lockThread");thread.start();}publicstaticvoidmain(String[]args)throwsIOException{BufferedReaderbufferedReader=newBufferedReader(newInputStreamReader(System.in));bufferedReader.readLine();busyThread();bufferedReader.readLine();lockThread(newObject());}}命令行排查我们使用top-H-ppid
来找到cpu使用率比较高的一些线程
占用率最高的线程ID为1586480,将其转换为16进制形式(因为javanative线程以16进制形式输出)
printf'%x\n'1586480
pid得到nid
接着直接在jstack中找到相应的堆栈信息
jstackpid|grep'nid'-C5
注:因为我本地环境是MacOs,且我们上面的例子比较简单,我们直接使用jstack也可以看到线程情况
当然,也可以使用工具,直接可视化的查看线程情况
通过Jprofiler的cpu负载,我们可以看到cpu负载一直巨高不下,如果排除了流量上涨的可能性,那就需要排查代码存在的问题。
当然,CPU问题,远远不是我上文例子这么简单,我们也仅仅介绍了最初级的问题排查思路。
也有许多的工具,适合不同场景下的问题排查思路,比如:
多种工具结合,逐步缩小问题范围,才是真正解决问题的处理方法,当然,更多的工具留给你自己去做尝试了。
线上内存,常见的有下面三类问题
新生代(Young)与老年代(Old)的比例的值为1:2(该值可以通过参数–XX:NewRatio来指定)
什么是内存溢出
当程序需要申请内存的时候,由于没有足够的内存,此时就会抛出OutOfMemoryError,这就是内存溢出。
下面我们来看一个例子
线上OOM问题排查,我们有两种方式
-XX:+HeapDumpOnOutOfMemoryError
通过Jprofiler,打开dump文件
内存泄漏指程序运行过程中分配内存给临时变量,用完之后却没有被GC回收,始终占着内存,即不能使用也不能分配给其他程序,就叫做内存泄漏(也就是相当于占着内存却不能被管理到造成内存的浪费)
内存泄漏短期内或者说轻微的不会有太大的影响,但内存泄漏堆积起来后却会很严重,会一直占用掉可用的内存,从而出现内存溢出的现象。
常见问题
排查思路可以参照上面内存溢出的情况,排查大对象即可。
关于垃圾回收,常见问题主要有两点
我们的调优目标,自然也是围绕这两个来
排查垃圾回收问题,启动服务时一定要加上如下四个参数
主要工具jstat,如下命令就是监控gc情况,每秒采样一次,采样10次
jstat-gc44017100010
为了方便大家看到区别,我手动出发了一次fullgc,大家也可以明显看到,FGC从0变为了1。
S0C/S1C、S0U/S1U、EC/EU、OC/OU、MC/MU分别代表两个Survivor区、Eden区、老年代、元数据区的容量和使用量。YGC/YGT、FGC/FGCT、GCT则代表YoungGc、FullGc的耗时和次数以及总耗时。如果看到gc比较频繁,再针对gc方面做进一步分析。
tcp队列溢出
netstat-s|egrep"listen|LISTEN"
使用telnetip/域名端口号来查看网络是否连接
如果出现下图内容,则证明网络已经连接
关于本文给出的例子,你一定要实践一遍,都非常的简单,亲自去使用文中提到的工具,只有实践了,你才是真正入门了。
在一些比较简单的业务场景下,排查系统性能问题相对来说简单,且容易找到具体原因。但在一些复杂的业务场景下,比如开源框架下的源码问题,相对来说就很难排查了,有时候通过工具只能猜测到可能是某些地方出现了问题,而实际排查则要结合源码做具体分析。
线上问题排查可以说没有捷径,排查线上的性能问题本身就不是一件很简单的事情,除了将今天介绍的这些工具融会贯通,还需要我们不断地去累积经验,才能够快速成长。