Java监控与性能优化(二) 性能分析

Posted by ZhouJ000 on November 15, 2020

Java性能优化01-程序优化
Java性能优化02-并行优化
Java性能优化03-JVM调优
Java性能优化04-调优工具

Java监控与性能优化(一) 概况
Java监控与性能优化(二) 性能分析
Java监控与性能优化(三) JVM与调优

性能分析

CPU使用率

要使应用的性能或扩展性达到最高,就必须充分利用分配给它的CPU周期。应用消耗很多CPU并不意味着性能或扩展性达到最高,要想找到应用如何使用CPU周期,可以在操作系统上监控CPU使用率。大多数操作系统CPU使用率分为用户态CPU使用率和系统态CPU使用率。用户态CPU使用率是指应用执行操作系统调用的时间占用总CPU的百分比。系统态CPU使用率高意味着共享资源有竞争或I/O设备之间有大量的交互,那么理想情况下,引用达到最高性能和扩展性时,它的系统态CPU使用率为0%

对于计算密集型应用来说,不仅监控用户态和系统态的CPU使用率,还要进一步监控每时钟指令数(IPC)或每指针时钟周期(CPI)等指标,这2个指标对于计算密集型应用来说很重要,因为现代操作系统自带的CPU使用率监控工具只能报告CPU使用率,而没有CPU执行指令占CPU始终周期的百分比。那么,即便CPU在等待内存中的数据,操作系统工具仍然会报告CPU繁忙,这种情况通常被称为停滞(Stall)。当CPU执行指令而所用的操作数据不在寄存器或缓存中时,就会发生停滞,指令执行必须等待数据从内存转入CPU寄存器,这样就会浪费时钟周期,CPU停滞通常会浪费几百个时钟周期。因此提高计算密集型应用性能的策略是减少停滞或改善CPU高速缓存使用率,从而减少CPU等待内存数据时浪费的时钟周期

Windows监控:Task Manager(任务管理器)和Performance Monitor(性能监视器)

Linux监控:GONME System Monitor、vmstat命令、mpstat命令、top命令

CPU调度程序运行队列

除了CPU使用率外,监控CPU调度队列对于分辨系统是否满负荷也有重要意义。运行队列中就是那些已准备好运行、正等待可用CPU的轻量级进程。如果准备运行的轻量级进程数超过系统所能处理的上限,运行队列就会很长,这就表明系统负载可能已经饱和。系统运行队列长度等于虚拟处理器的个数时,用户不会明显感到性能下降,此虚拟处理器的个数就是系统硬件线程的个数,即Java APIRuntime.availableProcessors()的返回值,当运行队列长度达到虚拟处理器的4倍或更多时,系统的响应就非常迟缓了

一般性是如果在很长一段时间里,运行队列的长度一直都超过虚拟处理器个数的1倍,就需要关注了,只是暂时不用立即采取行动。如果很长一段时间达到3~4倍甚至更高,拿就需要立即注意并采取行动。解决运行队列长有两种办法。一是增加CPU以分担负载或减少处理器的负载量。这种方法从根本上减少了每个虚拟处理器上的活动线程数,从而减少了运行队列中的轻量级进程数。另一种方法是分析系统中运行的应用,改进CPU使用率,也就是研究可以减少应用运行所需CPU周期的方法。比如减少垃圾收集器的频率或采用完成同样任务但CPU指令更少的算法,可以用更有效的算法和数据结构来实现更好的性能。这是因为现代JIT编译器可以产生成熟优化的代码以改善性能,但Java程序员几乎无法操作JIT编译器,所以应该关注算法和数据结构的效率,可以通过性能分析找出哪些算法和数据结构值得关注

Linux监控:vmstat

内存使用率

除了CPU使用率,还要监控系统内存相关的属性。例如页面调度或页面交换、加锁、线程迁移中的让步式和抢占式的上下文切换

页面交换

系统在进行页面交换或使用虚拟内存时,Java应用或JVM会表现出明显的性能问题。当应用运行所需的内存超过可用物理内存时,就会发生页面交换。为了应对这种可能出现的情况,通常要为系统配置swap空间。swap空间一般会在一个独立的磁盘分区上。当应用耗尽物理内存时,操作系统会将应用的一部分置换到磁盘上的swap空间,通常是应用中最少运行的部分,以免影响整个应用或者应用最忙的那部分。当访问应用中被置换出去的部分时,就必须将它从磁盘置换进内存,而这种置换活动会对应用的响应性和吞吐量造成很大影响

JVM垃圾收集器在系统页面交换时的性能也很差,这是由于垃圾收集器为了回收不可达对象所占的空间,需要访问大量的内存。如果Java堆的一部分被置换出去,就必须先置换进内存以便垃圾收集器扫描存活对象,这会增加垃圾收集的持续时间。垃圾收集是一种Stop-The-World(时空停滞)操作,即停止所有正在运行的应用线程,如果此时系统正在进行页面交换,则会引起JVM长时间的停顿

如果发现垃圾收集时间变长,系统有可能正在进行页面交换,为了验证这一点,必须监控系统的页面交换

Linux监控:vmstat监控si和so

锁竞争

优化之后的现代JVM已经改善了应用遇到锁竞争时的性能。Java Hotspot VM以用户代码而不是直接依赖操作系统锁原语的方式实现了许多锁优化逻辑、Java同步方法及同步块。比如增加的锁优化机制:线程通过忙循环自旋(Tight Loop Spin)尝试获得锁,如果若干次忙循环自旋之后任然没有成功,则挂起该线程,等待被唤醒再次尝试获取该锁。挂起和唤醒线程会导致操作系统的让步式上下文切换。让步式上下文的切换耗费的时钟周期代价非常高,通常高达80000个时钟周期。可以遵循以下的一般性准则,对于任何Java应用来说,如果让步式上下文切换占去它5%或更多的可用时钟周期,说明它可能遇到了锁竞争

Linux监控:pidstat

抢占式上下文切换

让步式上下文切换时指执行线程主动释放CPU,抢占式上下文切换时指线程因为分配的时间片用尽而被迫放弃CPU或者被其他优先级更高的线程锁抢占

线程迁移

待运行线程在处理器之前的迁移也会导致性能的下降。大多数操作系统的CPU调度程序会将待运行线程分配给上次运行它的虚拟处理器。如果这个虚拟处理器忙,调度程序就会将待处理线程迁移到其他可用的虚拟处理器。线程迁移会对应用性能造成影响,这是因为新的虚拟处理器缓存中可能没有待运行线程所需的数据或状态信息。多核系统上运行Java应用可能会发生大量的线程迁移,减少迁移的策略是创建处理器组并将应用分配给这些处理器组。一般性准则是,如果横跨多核或虚拟处理器的Java应用每秒迁移超过500次,将Java应用绑定在处理器组上就有好处

网络I/O使用率

分布式Java应用的性能和扩展性受限于网络带宽或网络I/O的性能。举例来说,如果发送到系统网络接口硬件的消息量超过了它的处理能力,消息就会进入操作系统的缓冲区,这会导致应用延迟。此外网络上发生的其他状况也会导致延迟

单次读写数据量小而网络读写量大的应用会消耗大量的系统态CPU,产生大量的系统调用。对于这类应用,减少系统态CPU的策略是减少网络读写的系统调用。此外,使用非阻塞的Java NIO而不是阻塞的java.net.Socket,减少处理请求和发送相应的线程数,也可以改善应用性能

从非阻塞Socket中读取数据的策略是,应用在每次读请求时尽可能多地读取数据。同样,当往Socket中写数据时,每个写调用应该尽可能多地写

Linux监控:netstat或sysstat

磁盘I/O使用率

对于有磁盘操作的应用来说,查找性能问题,就应该监控磁盘I/O。一些应用的核心功能需要大量使用硬盘,比如数据库。其中磁盘I/O使用率是最有用的监控数据。磁盘I/O使用率,即磁盘处于活动时间的百分比,磁盘在数据传输和处理命令(如寻道)时处于活动状态。磁盘利用率与资源争用程度成正比,与性能成反比。也就是说磁盘利用率越高,资源争用就越严重,性能也就越差,响应时间就越长。一般来说,如果磁盘利用率超过70%,应用进程将花费较长的时间等待I/O完成,因为绝大多数进程在等待过程中将被阻塞或休眠

如果应用的磁盘I/O使用率高,就值得深入分析系统磁盘I/O子系统的性能,进一步查看它预期的负载量、磁盘服务时间、寻道时间以及服务I/O事件的时间。如果需要改善磁盘使用率,可以使用一些策略。从硬件和操作系统上看,下面是一些改进磁盘I/O使用率的策略: 1、更快的存储设备
2、文件系统扩展到多个磁盘
3、操作系统调优使得可以缓存大量的文件系统数据结构

关于磁盘性能,还有一个经常被忽视的方法,就是检查磁盘缓存是否开启。有一些系统将磁盘缓存设置为禁用。开启磁盘缓存可以改善严重依赖磁盘I/O的应用的性能。然而,如果发现系统默认设置为禁用磁盘缓存,你应该加以注意,因为一旦开启磁盘缓存,意外电源故障可能会造成数据损坏

从应用角度看,任何减少磁盘活动的策略都是有帮助的,例如使用带缓存的输入输出流以减少读写操作次数,或在应用中集成缓存的数据结构以减少或消除磁盘交互。缓冲流减少了调用操作系统的次数从而降低系统态的CPU使用率。虽然这不会改善磁盘I/O性能,但可以使更多CPU周期用于应用的其他部分或者其他运行的应用。JDK提供了缓冲数据结构,也容易使用,如java.io.BufferedInputStreamjava.io.BufferedOutputStream

Linux监控:iostat

参考:
《Java性能优化权威指南》