发表文章

[最新] 初探调优2:Java应用调优思路与工具

a724888 3月前 1

对于调优这个事情来说,一般就是三个过程:

  • 性能监控:问题没有发生,你并不知道你需要调优什么?此时需要一些系统、应用的监控工具来发现问题。
  • 性能分析:问题已经发生,但是你并不知道问题到底出在哪里。此时就需要使用工具、经验对系统、应用进行瓶颈分析,以求定位到问题原因。
  • 性能调优:经过上一步的分析定位到了问题所在,需要对问题进行解决,使用代码、配置等手段进行优化。

Java调优也不外乎这三步。

此外,本文所讲的性能分析、调优等是抛开以下因素的:

  • 系统底层环境:硬件、操作系统等
  • 数据结构和算法的使用
  • 外部系统如数据库、缓存的使用
  • Java中一些Api的使用,如Random、StringBuilder等。

调优准备

调优是需要做好准备工作的,毕竟每一个应用的业务目标都不尽相同,性能瓶颈也不会总在同一个点上。在业务应用层面,我们需要:

  • 需要了解系统的总体架构,明确压力方向。比如系统的哪一个接口、模块是使用率最高的,面临高并发的挑战。
  • 需要构建测试环境来测试应用的性能,使用ab、loadrunner、jmeter都可以。
  • 对关键业务数据量进行分析,这里主要指的是对一些数据的量化分析,如数据库一天的数据量有多少;缓存的数据量有多大等
  • 了解系统的响应速度、吞吐量、TPS、QPS等指标需求,比如秒杀系统的相应速度和QPS是要求非常高的。
  • 了解系统相关软件的版本、模式和参数等,有时候限于应用依赖服务的版本、模式等,性能也会受到一定的影响。

此外,我们还需要了解Java相关的一些知识:

性能分析

在系统层面能够影响应用性能的一般包括三个因素:CPU、内存和IO,可以从这三方面进行程序的性能瓶颈分析。

CPU分析

当程序响应变慢的时候,首先使用top、vmstat、ps等命令查看系统的cpu使用率是否有异常,从而可以判断出是否是cpu繁忙造成的性能问题。其中,主要通过us(用户进程所占的%)这个数据来看异常的进程信息。当us接近100%甚至更高时,可以确定是cpu繁忙造成的响应缓慢。一般说来,cpu繁忙的原因有以下几个:

  • 线程中有无限空循环、无阻塞、正则匹配或者单纯的计算
  • 发生了频繁的gc
  • 多线程的上下文切换

确定好cpu使用率最高的进程之后就可以使用jstack来打印出异常进程的堆栈信息:

jstack [pid]

jstack

接下来需要注意的一点是,Linux下所有线程最终还是以轻量级进程的形式存在系统中的,而使用jstack只能打印出进程的信息,这些信息里面包含了此进程下面所有线程(轻量级进程-LWP)的堆栈信息。因此,进一步的需要确定是哪一个线程耗费了大量cpu,此时可以使用top -p [processId]来查看,也可以直接通过ps -Le来显示所有进程,包括LWP的资源耗费信息。最后,通过在jstack的输出文件中查找对应的lwp的id即可以定位到相应的堆栈信息。其中需要注意的是线程的状态:RUNNABLE、WAITING等。对于Runnable的进程需要注意是否有耗费cpu的计算。对于Waiting的线程一般是锁的等待操作。

也可以使用jstat来查看对应进程的gc信息,以判断是否是gc造成了cpu繁忙。

jstat -gcutil [pid]

jstat

还可以通过vmstat,通过观察内核状态的上下文切换(cs)次数,来判断是否是上下文切换造成的cpu繁忙。

vmstat 1 5

jstat

内存分析

对Java应用来说,内存主要是由堆外内存和堆内内存组成。

  1. 堆外内存

    堆外内存主要是JNI、Deflater/Inflater、DirectByteBuffer(nio中会用到)使用的。对于这种堆外内存的分析,还是需要先通过vmstat、sar、top、pidstat等查看swap和物理内存的消耗状况再做判断的。此外,对于JNI、Deflater这种调用可以通过Google-preftools来追踪资源使用状况。

  2. 堆内内存

    此部分内存为Java应用主要的内存区域。通常与这部分内存性能相关的有:

    • 创建的对象:这个是存储在堆中的,需要控制好对象的数量和大小,尤其是大的对象很容易进入老年代
    • 全局集合:全局集合通常是生命周期比较长的,因此需要特别注意全局集合的使用
    • 缓存:缓存选用的数据结构不同,会很大程序影响内存的大小和gc
    • ClassLoader:主要是动态加载类容易造成永久代内存不足
    • 多线程:线程分配会占用本地内存,过多的线程也会造成内存不足

    以上使用不当很容易造成:

    • 频繁GC -> Stop the world,使你的应用响应变慢
    • OOM,直接造成内存溢出错误使得程序退出。OOM又可以分为以下几种:
      • Heap space:堆内存不足
      • PermGen space:永久代内存不足
      • Native thread:本地线程没有足够内存可分配

    排查堆内存问题的常用工具是jmap,是jdk自带的。一些常用用法如下:

    • 查看jvm内存使用状况:jmap -heap
    • 查看jvm内存存活的对象:jmap -histo:live
    • 把heap里所有对象都dump下来,无论对象是死是活:jmap -dump:format=b,file=xxx.hprof
    • 先做一次full GC,再dump,只包含仍然存活的对象信息:jmap -dump:format=b,live,file=xxx.hprof

    此外,不管是使用jmap还是在OOM时产生的dump文件,可以使用Eclipse的MAT(MEMORY ANALYZER TOOL)来分析,可以看到具体的堆栈和内存中对象的信息。当然jdk自带的jhat也能够查看dump文件,并启动web端口供浏览器浏览。

IO分析

通常与应用性能相关的包括:文件IO和网络IO。

  1. 文件IO

    可以使用系统工具pidstat、iostat、vmstat来查看io的状况。这里可以看一张使用vmstat的结果图。

    这里主要注意bi和bo这两个值,分别表示块设备每秒接收的块数量和块设备每秒发送的块数量,由此可以判定io繁忙状况。进一步的可以通过使用strace工具定位对文件io的系统调用。通常,造成文件io性能差的原因不外乎:

  2. 网络IO

    查看网络io状况,一般使用的是netstat工具。可以查看所有连接的状况、数目、端口信息等。例如:当time_wait或者close_wait连接过多时,会影响应用的相应速度。

     netstat -anp
    

    此外,还可以使用tcpdump来具体分析网络io的数据。当然,tcpdump出的文件直接打开是一堆二进制的数据,可以使用wireshark阅读具体的连接以及其中数据的内容。

     tcpdump -i eth0 -w tmp.cap -tnn dst port 8080 #监听8080端口的网络请求并打印日志到tmp.cap中
    

    还可以通过查看/proc/interrupts来获取当前系统使用的中断的情况。

    各个列依次是:

     irq的序号, 在各自cpu上发生中断的次数,可编程中断控制器,设备名称(request_irq的dev_name字段)
    

    通过查看网卡设备的终端情况可以判断网络io的状况。

其他分析工具

上面分别针对CPU、内存以及IO讲了一些系统/JDK自带的分析工具。除此之外,还有一些综合分析工具或者框架可以更加方便我们对Java应用性能的排查、分析、定位等。

  • VisualVM

    这个工具应该是Java开发者们非常熟悉的一款java应用监测工具,原理是通过jmx接口来连接jvm进程,从而能够看到jvm上的线程、内存、类等信息。如果想进一步查看gc情况,可以安装visual gc插件。此外,visualvm也有btrace的插件,可以可视化直观的编写btrace代码并查看输出日志。 与VisualVm类似的,jconsole也是通过jmx查看远程jvm信息的一款工具,更进一步的,通过它还可以显示具体的线程堆栈信息以及内存中各个年代的占用情况,也支持直接远程执行MBEAN。当然,visualvm通过安装jconsole插件也可以拥有这些功能。但由于这俩工具都是需要ui界面的,因此一般都是通过本地远程连接服务器jvm进程。服务器环境下,一般并不用此种方式。

  • Java Mission Control(jmc)

    此工具是jdk7 u40开始自带的,原来是JRockit上的工具,是一款采样型的集诊断、分析和监控与一体的非常强大的工具。docs.oracle.com/javacompone…

  • Btrace 这里不得不提的是btrace这个神器,它使用java attach api+ java agent + instrument api能够实现jvm的动态追踪。在不重启应用的情况下可以加入拦截类的方法以打印日志等。具体的用法可以参考Btrace入门到熟练小工完全指南

  • Jwebap

    Jwebap是一款JavaEE性能检测框架,基于asm增强字节码实现。支持:http请求、jdbc连接、method的调用轨迹跟踪以及次数、耗时的统计。由此可以获取最耗时的请求、方法,并可以查看jdbc连接的次数、是否关闭等。但此项目是2006年的一个项目,已经将近10年没有更新。根据笔者使用,已经不支持jdk7编译的应用。如果要使用,建议基于原项目二次开发,同时也可以加入对redis连接的轨迹跟踪。当然,基于字节码增强的原理,也可以实现自己的JavaEE性能监测框架。

    上图来自笔者公司二次开发过的jwebap,已经支持jdk8和redis连接追踪。

  • useful-scripts

    这里有一个本人参与的开源的项目:github.com/superhj1987…,封装了很多常用的性能分析命令,比如上文讲的打印繁忙java线程堆栈信息,只需要执行一个脚本即可。

相关推荐
最新评论 (0)
返回
发表文章
a724888
文章数
346
评论数
0
注册排名
662395