当前位置: 首页 > 后端技术 > Java

Java程序员涨薪必备的性能调优知识点收集起来!

时间:2023-04-01 18:50:37 Java

Java应用程序性能优化是一个老生常谈的话题。典型的性能问题包括页面响应慢、接口超时、服务器负载高、并发度低、数据库死锁频繁等。尤其是在今天“粗暴而快速”的互联网开发模式下,随着系统访问量的增加和代码的日益臃肿,各种性能问题开始浮出水面。Java应用性能存在很多瓶颈,比如磁盘、内存、网络I/O等系统因素,Java应用代码、JVMGC、数据库、缓存等。将Java性能优化分为四个层次:应用层、数据库层、框架层和JVM层。每层优化的难度逐级递增,所涉及的知识和解决的问题也会有所不同。应用层需要了解代码逻辑,通过Java线程栈定位有问题的代码行等;数据库层需要分析SQL,定位死锁等;框架层需要看懂源码,了解框架机制;JVM层需要了解GC的类型和工作机制,深入了解JVM各种参数的作用。围绕Java性能优化,最基本的分析方法有两种:现场分析和事后分析。现场分析法是保留现场,然后利用诊断工具进行分析定位。现场分析对线上影响较大,有些场景(特别是涉及用户线上重点业务时)不适合。事后分析包括尽可能多地收集现场数据,然后立即恢复服务,并对收集到的现场数据进行事后分析和复现。下面我们从性能诊断工具入手,分享和回顾HeapDump性能社区中的一些经典案例和实践。性能诊断工具性能诊断一种是对已经确定存在性能问题的系统和代码进行诊断,另一种是提前对预上线系统进行性能测试,判断性能是否满足上线要求。本文主要关注前者,后者可以通过各种性能压力测试工具(如JMeter)进行测试,不在本文讨论范围之内。对于Java应用,性能诊断工具主要分为两层:OS层和Java应用层(包括应用代码诊断和GC诊断)。OSDiagnosisOS诊断主要集中在三个方面:CPU、Memory和I/O。CPU诊断主要关注CPU的平均负载(LoadAverage)、CPU使用率和上下文切换次数(ContextSwitch)。您可以使用top命令查看系统平均负载和CPU使用率。top_x集成在PerfMa开源的XPocket插件容器中,是linuxtop的增强版,可以显示CPU使用率/负载,以及使用的CPU和内存进程列表。本插件将繁杂的top命令输出功能拆分整理,更清晰易用,支持pipeline,尤其是top进程或线程的tid、pid可以直接获取;mem_s命令增加了根据进程swapsize的大小排序,增强了原有的top功能。图表显示当前系统的CPU使用率超过51%。当你发现某些进程占用cpu比较高时,可以使用top_x的cpu_t命令,它会自动获取当前cpu占用最高的进程的cpu状态。也可以通过-p参数指定进程pid,使用cpu_t可以直接看到:vmstat命令可以查看CPU的上下文切换次数,XPocket也集成了vmstat工具。发生上下文切换次数的场景主要有:时间片用完,CPU正常调度下一个任务被其他优先级更高的任务抢占执行任务遇到I/O阻塞,挂起当前任务,并切换到下一个任务用户代码主动挂起当前任务放弃CPU多任务抢占资源,因为挂起的硬件中断没有抢到。Java线程上下文切换主要来源于对共享资源的竞争。通常,锁定单个对象很少会成为系统瓶颈,除非锁定粒度太大。但是,在一个访问频率高且连续锁定多个对象的代码块中,可能会发生大量的上下文切换,成为系统瓶颈。作者朱继兵的CPU上下文切换导致服务雪崩文章记录了在log4j中使用异步AsyncLogger写日志导致CPU上下文频繁切换,最终导致服务雪崩的案例。AsyncLogger使用了disruptor框架,disruptor框架在核心数据结构RingBuffer上处理MultiProducer。写入日志时需要使用Sequence,但此时RingBuffer已满,无法获取Sequence,disruptor会调用Unsafe.park主动挂起当前线程。简单来说就是当消费速度跟不上生产速度时,生产线程以1nano的重试间隔进行无限重试,导致CPU频繁挂起和唤醒,造成大量CPU切换和占用CPU资源。通过将Distuptor版本和og4j2版本分别更改为3.3.6和2.7解决了该问题。内存从操作系统的角度来看,内存关系到应用进程是否足够,可以使用free-m命令查看内存使用情况。通过top命令可以查看进程使用的虚拟内存VIRT和物理内存RES。根据公式VIRT=SWAP+RES,可以计算出具体应用使用的交换分区(Swap)。如果交换分区太大,会影响Java应用程序的性能。你可以将swappiness的值调的越小越好。因为对于Java应用来说,占用过多的swap分区可能会影响性能,毕竟磁盘性能比内存慢很多。I/OI/O包括磁盘I/O和网络I/O,磁盘一般更容易出现I/O瓶颈。可以通过iostat查看磁盘的读写状态,通过CPU的I/O等待可以查看磁盘I/O是否正常。如果磁盘I/O一直处于高状态,说明磁盘太慢或有故障,已经成为性能瓶颈,需要进行应用优化或更换磁盘。除了常用的top、ps、vmstat、iostat等命令外,还有其他可以诊断系统问题的Linux工具,如mpstat、tcpdump、netstat、pidstat、sar等,这里总结一下针对不同类型Linux设备的性能诊断工具,如下图所示,供参考。Java应用诊断工具应用代码诊断应用代码性能问题是比较容易解决的一类性能问题。通过一些应用层面的监控告警,如果确定有问题的功能和代码,可以直接通过代码定位;或者用top+jstack找出有问题的线程栈,定位到有问题线程的代码,也能找到问题所在。对于比较复杂和逻辑的代码段,通过Stopwatch打印性能日志往往可以定位到大部分应用代码性能问题。常用的Java应用程序诊断包括对线程、堆栈和GC的诊断。jstackjstack命令通常与top结合使用,通过top-H-ppid定位Java进程和线程,然后使用jstack-lpid导出线程栈。由于线程堆栈是瞬态的,因此需要多次转储,通常是3次转储,通常每5秒一次。将top定位的Java线程pid转成16进制,得到Java线程栈中的nid,找到对应的问题线程栈。jstack_x工具集成在XPocket中。可以使用stack-tnid命令查看某个线程等待锁的调用栈,通过调用栈定位到业务代码。XElephant和XSheepdogXElephant是HeapDmp性能社区提供的Java内存转储文件的免费在线分析产品。可以让内存中对象之间的各种依赖关系更加清晰,无需安装软件,提供上传方式,不受本机内存限制,支持超大Dump文件的分析。XSheepdog是HeapDmp性能社区免费提供的在线threaddump文件分析产品。梳理了线程、线程池、栈、方法、锁之间的关系,多角度呈现给用户,让线程问题一目了然。GC诊断JavaGC解决了程序员管理内存的风险,但GC引起的应用程序暂停是另一个需要解决的问题。JDK提供了一系列工具来定位GC问题。比较常用的有jstat、jmap和第三方工具MAT。jstatjstat命令可以打印GC详情、YoungGC和FullGC的次数、堆信息等,命令格式为jstat–gcxxx-tpid。MATMAT是分析Java堆的强大工具。它提供直观的诊断报告。内置的OQL允许在堆上进行类似SQL的查询。它具有强大的功能。传出引用和传入引用可以追溯对象引用的来源。MAT有两列显示对象的大小,分别是Shallowsize和Retainedsize。前者表示对象本身占用的内存大小,不包括它引用的对象。后者是对象本身及其直接或间接引用的对象的Shallow大小的总和,即对象被回收后GC释放的内存大小。一般来说,注意后者的大小就够了。对于一些大堆(几十G)的Java应用,打开MAT需要更大的内存。通常本地开发机内存太小打不开。建议在离线服务器上安装图形环境和MAT,远程打开查看。或者执行mat命令生成堆索引,并将索引复制到本地,但是这种方式看到的堆信息有限。为了诊断GC问题,建议在JVM参数中加上-XX:+PrintGCDateStamps。对于Java应用,大部分应用和内存问题都可以通过top+jstack+jmap+MAT来定位,这是必不可少的工具。Java应用诊断有时需要参考OS相关信息,可以使用一些更全面的诊断工具,如Zabbix(集成OS和JVM监控)等。在分布式环境中,还需要分布式跟踪系统等基础设施。为应用性能诊断提供强有力的支持。性能优化实践介绍完一些常用的性能诊断工具,下面将结合我们在Java应用调优方面的一些实践,从JVM层、应用代码层、数据库层分享案例。JVM调优:GC之痛作者阿飞Javaer的《FullGC实战:业务小姐姐看图一直在转圈》(https://heapdump.cn/article/2...)记载,接口取了??一个时间长导致无法访问镜像,排除数据库、同步日期、系统问题后,开始排查GC问题。使用jstat命令后的输出如下bash-4.4$/app/jdk1.8.0_192/bin/jstat-gc12sS0CS1CS0US1UECEUOCOUMCMUCCSCCCSUYGCYGCTFGCFGCTGCT170496.0170496.00.00.0171008.0130368.91024000.0590052.870016.068510.88064.07669.098313.9611400275.606289.567170496.0170496.00.00.0171008.041717.21024000.0758914.970016.068510.88064.07669.098714.0111401275.722289.733170496.0170496.00.00.0171008.0126547.21024000.0770587.270016.068510.88064.07669.099014.0911403275.986290.077170496.0170496.00.00.0171008.045488.71024000.0650767.070016.068531.98064.07669.099414.1481405276.222290.371170496.0170496.00.00.0171008.0146029.11024000.0714857.270016.068531.98064.07669.099514.1661406276.366290.531170496.0170496.00.00.0171008.0118073.51024000.0669163.270016.068531.98064.07669.099814.2261408276.736290.962170496.0170496.00.00.0171008.03636.11024000.0687630.070016.068535.68064.07669.6100114.3421409276.871291.213170496.0170496.00.00.0171008.087247.21024000.0704977.570016.068535.68064.07669.6100514.4631411277.099291.562几乎每1每秒有一次FGC,停顿时间挺长的。最后关闭参数-XX:-UseAdaptiveSizePolicy,优化后重启服务,访问速度又变快了。对于高并发、大数据量交互的应用,GC调优还是很有必要的,尤其是默认的JVM参数通常不能满足业务需求,需要专门调优。GC日志的解读公开资料很多,本文不再赘述。GC调优目标基本上有3种思路:降低GC频率,可以增加堆空间,减少不必要的对象生成;减少GC停顿时间,可以减少堆空间,使用CMSGC算法;避免FullGC,调整CMStriggerRatio,避免PromotionFailure和Concurrentmodefailure(老年代分配更多空间,增加GC线程数加速回收),减少大对象的产生等应用层调优:闻到坏代码的味道从应用层代码调优入手,分析代码效率下降的根本原因,无疑是提升Java应用性能的最佳途径之一。FGC实战:BadCode导致服务频繁和FGC无响应问题分析本文记录了BadCode导致内存泄漏、CPU占用率高、接口大量超时的案例。使用MAT工具分析jvm堆,从上面的饼图可以看出大部分堆内存都被同一个内存占用,再查看堆内存的详细情况,回溯到上层,很快找到了罪魁祸首。找到内存泄漏的对象,在项目中全局搜索对象名,是一个Bean对象,然后定位到它的一个Map类型的属性。这个Map使用ArrayList按照类型存储各个检测接口的响应结果。每次检测后,塞入ArrayList中进行分析。由于Bean对象不会被回收,而且这个属性也没有清除逻辑,所以十多天没有使用了。在上线重启的情况下,Map会越来越大,直到占满内存。内存满后,无法再为HTTP响应结果分配内存,所以一直卡在readLine。而我们这个I/O比较多的接口,告警次数特别多,估计跟响应大需要更多的内存有关。对于坏代码的定位,除了常规意义上的codereview之外,还可以通过MAT等工具在一定程度上快速定位系统性能瓶颈。但在一些与特定场景或业务数据绑定的情况下,需要辅助代码走查、性能测试工具、数据模拟,甚至线上引流,最终确认性能问题的根源。以下是我们总结的一些不良代码的一些可能特征,供大家参考:(1)代码可读性差,没有基本的编程规范;(2)生成对象过多或生成大对象,内存泄漏等;(3)IO流操作太多,或者忘记关闭;(4)数据库操作过多,事务过长;(5)同步使用错误的场景;(6)耗时的循环迭代操作等数据库层调优:死锁噩梦对于大多数Java应用来说,与数据库交互的场景是很常见的,尤其是OLTP等对数据一致性要求高的应用,其性能数据库将直接影响整个应用程序的性能。表现。一般来说,对于数据库层的调优,我们基本上从以下几个方面入手:(1)SQL语句层面的优化:慢SQL分析、索引分析调优、事务拆分等;数据库配置层面的优化:如字段设计、缓存大小调整、磁盘I/O等数据库参数优化、数据碎片整理等;(3)数据库结构层面的优化:考虑数据库的垂直拆分和水平拆分等;(4)选择合适的数据库引擎或类型以适应不同的场景,比如考虑引入NoSQL。总结与建议性能调优也遵循2-8原则,80%的性能问题是由20%的代码产生的,所以优化关键代码更有效。同时性能优化要按需优化,过度优化可能会引入更多的问题。对于Java性能优化,不仅需要了解系统架构和应用代码,还需要关注JVM层乃至操作系统底层。总结起来,主要可以考虑以下几点:1)基础性能调优这里的基础性能指的是硬件层面或者操作系统层面的升级优化,比如网络调优、操作系统版本升级、硬件设备优化等.比如F5的使用和SDD硬盘的引入,包括新版Linux在NIO方面的升级,都可以极大促进应用的性能提升;2)数据库性能优化包括常见的事务拆分、索引调优、SQL优化、NoSQL的引入等,比如事务拆分时异步处理的引入,最后实现一致性的方法的引入,包括引入针对特定场景的各种NoSQL数据库,可以大大缓解传统数据库在高并发下的缺点;3)应用架构优化引入一些新的计算或存储框架,使用新特性解决原有的集群计算性能瓶颈等;或引入分布式策略对计算和存储进行分级,包括预计算和预处理等,采用典型的空间变化时间等做法;可以在一定程度上降低系统负载;4)业务层面的优化技术并不是提高系统性能的唯一手段。在很多出现性能问题的场景中,可以看出很大一部分是因为特殊的业务场景导致的,如果能够在业务中避免或者调整,往往是最有效的。