java高性能调优(Java性能调优的7个实用技巧分享)java基础 / Java Web开发中的性能调优...

wufei123 发布于 2024-07-03 阅读(6)

随着应用的数据量不断的增加,系统的反应一般会越来越慢,这个时候我们就需要性能调优。性能调优的步骤如下:

Java 应用性能优化是一个老生常谈的话题,典型的性能问题如页面响应慢、接口超时,服务器负载高、并发数低,数据库频繁死锁等尤其是在“糙快猛”的互联网开发模式大行其道的今天,随着系统访问量的日益增加和代码的臃肿,各种性能问题开始纷至沓来。

Java 应用性能的瓶颈点非常多,比如磁盘、内存、网络 I/O 等系统因素,Java 应用代码,JVM GC,数据库,缓存等笔者根据个人经验,将 Java 性能优化分为 4 个层级:应用层、数据库层、框架层、JVM 层,如图 1 所示。

图 1.Java 性能优化分层模型每层优化难度逐级增加,涉及的知识和解决的问题也会不同比如应用层需要理解代码逻辑,通过 Java 线程栈定位有问题代码行等;数据库层面需要分析 SQL、定位死锁等;框架层需要懂源代码,理解框架机制;JVM 层需要对 GC 的类型和工作机制有深入了解,对各种 JVM 参数作用了然于胸。

围绕 Java 性能优化,有两种最基本的分析方法:现场分析法和事后分析法现场分析法通过保留现场,再采用诊断工具分析定位现场分析对线上影响较大,部分场景(特别是涉及到用户关键的在线业务时)不太合适事后分析法需要尽可能多收集现场数据,然后立即恢复服务,同时针对收集的现场数据进行事后分析和复现。

下面我们从性能诊断工具出发,分享一些案例与实践一、性能诊断工具性能诊断一种是针对已经确定有性能问题的系统和代码进行诊断,还有一种是对预上线系统提前性能测试,确定性能是否符合上线要求本文主要针对前者,后者可以用各种性能压测工具(例如 JMeter)进行测试,不在本文讨论范围内。

针对 Java 应用,性能诊断工具主要分为两层:OS 层面和 Java 应用层面(包括应用代码诊断和 GC 诊断)OS 诊断OS 的诊断主要关注的是 CPU、Memory、I/O 三个方面二、 CPU 诊断。

对于 CPU 主要关注平均负载(Load Average),CPU 使用率,上下文切换次数(Context Switch)通过 top 命令可以查看系统平均负载和 CPU 使用率,图 2 为通过 top 命令查看某系统的状态。

图 2.top 命令示例平均负载有三个数字:63.66,58.39,57.18,分别表示过去 1 分钟、5 分钟、15 分钟机器的负载按照经验,若数值小于 0.7*CPU 个数,则系统工作正常;若超过这个值,甚至达到 CPU 核数的四五倍,则系统的负载就明显偏高。

图 2 中 15 分钟负载已经高达 57.18,1 分钟负载是 63.66(系统为 16 核),说明系统出现负载问题,且存在进一步升高趋势,需要定位具体原因了通过 vmstat 命令可以查看 CPU 的上下文切换次数,如图 3 所示:。

图 3.vmstat 命令示例上下文切换次数发生的场景主要有如下几种:1)时间片用完,CPU 正常调度下一个任务;2)被其它优先级更高的任务抢占;3)执行任务碰到 I/O 阻塞,挂起当前任务,切换到下一个任务;

4)用户代码主动挂起当前任务让出 CPU;5)多任务抢占资源,由于没有抢到被挂起;6)硬件中断Java 线程上下文切换主要来自共享资源的竞争一般单个对象加锁很少成为系统瓶颈,除非锁粒度过大但在一个访问频度高,对多个对象连续加锁的代码块中就可能出现大量上下文切换,成为系统瓶颈。

三、 Memory从操作系统角度,内存关注应用进程是否足够,可以使用 free –m 命令查看内存的使用情况通过 top 命令可以查看进程使用的虚拟内存 VIRT 和物理内存 RES,根据公式 VIRT = SWAP + RES 可以推算出具体应用使用的交换分区(Swap)情况,使用交换分区过大会影响 Java 应用性能,可以将 swappiness 值调到尽可能小。

因为对于 Java 应用来说,占用太多交换分区可能会影响性能,毕竟磁盘性能比内存慢太多四、 I/OI/O 包括磁盘 I/O 和网络 I/O,一般情况下磁盘更容易出现 I/O 瓶颈通过 iostat 可以查看磁盘的读写情况,通过 CPU 的 I/O wait 可以看出磁盘 I/O 是否正常。

如果磁盘 I/O 一直处于很高的状态,说明磁盘太慢或故障,成为了性能瓶颈,需要进行应用优化或者磁盘更换除了常用的 top、 ps、vmstat、iostat 等命令,还有其他 Linux 工具可以诊断系统问题,如 mpstat、tcpdump、netstat、pidstat、sar 等。

Brendan 总结列出了 Linux 不同设备类型的性能诊断工具,如图 4 所示,可供参考

图 4.Linux 性能观测工具五、 Java 应用诊断及工具应用代码性能问题是相对好解决的一类性能问题通过一些应用层面监控报警,如果确定有问题的功能和代码,直接通过代码就可以定位;或者通过 top+jstack,找出有问题的线程栈,定位到问题线程的代码上,也可以发现问题。

对于更复杂,逻辑更多的代码段,通过 Stopwatch 打印性能日志往往也可以定位大多数应用代码性能问题常用的 Java 应用诊断包括线程、堆栈、GC 等方面的诊断jstackjstack 命令通常配合 top 使用,通过 top -H -p pid 定位 Java 进程和线程,再利用 jstack -l pid 导出线程栈。

由于线程栈是瞬态的,因此需要多次 dump,一般 3 次 dump,一般每次隔 5s 就行将 top 定位的 Java 线程 pid 转成 16 进制,得到 Java 线程栈中的 nid,可以找到对应的问题线程栈。

图 5. 通过 top –H -p 查看运行时间较长 Java 线程如图 5 所示,其中的线程 24985 运行时间较长,可能存在问题,转成 16 进制后,通过 Java 线程栈找到对应线程 0x6199 的栈如下,从而定位问题点,如图 6 所示。

图 6.jstack 查看线程堆栈JProfilerJProfiler 可对 CPU、堆、内存进行分析,功能强大,如图 7 所示。同时结合压测工具,可以对代码耗时采样统计。

图 7. 通过 JProfiler 进行内存分析六、 GC 诊断Java GC 解决了程序员管理内存的风险,但 GC 引起的应用暂停成了另一个需要解决的问题JDK 提供了一系列工具来定位 GC 问题,比较常用的有 jstat、jmap,还有第三方工具 MAT 等。

jstatjstat 命令可打印 GC 详细信息,Young GC 和 Full GC 次数,堆信息等其命令格式为jstat –gcxxx -t pid ,如图 8 所示。

图 8.jstat 命令示例jmapjmap 打印 Java 进程堆信息 jmap –heap pid通过 jmap –dump:file=xxx pid 可 dump 堆到文件,然后通过其它工具进一步分析其堆使用情况。

MATMAT 是 Java 堆的分析利器,提供了直观的诊断报告,内置的 OQL 允许对堆进行类 SQL 查询,功能强大,outgoing reference 和 incoming reference 可以对对象引用追根溯源。

图 9.MAT 示例图 9 是 MAT 使用示例,MAT 有两列显示对象大小,分别是 Shallow size 和 Retained size,前者表示对象本身占用内存的大小,不包含其引用的对象,后者是对象自己及其直接或间接引用的对象的 Shallow size 之和,即该对象被回收后 GC 释放的内存大小,一般说来关注后者大小即可。

对于有些大堆 (几十 G) 的 Java 应用,需要较大内存才能打开 MAT通常本地开发机内存过小,是无法打开的,建议在线下服务器端安装图形环境和 MAT,远程打开查看或者执行 mat 命令生成堆索引,拷贝索引到本地,不过这种方式看到的堆信息有限。

为了诊断 GC 问题,建议在 JVM 参数中加上-XX:+PrintGCDateStamps。常用的 GC 参数如图 10 所示。

图 10. 常用 GC 参数对于 Java 应用,通过 top+jstack+jmap+MAT 可以定位大多数应用和内存问题,可谓必备工具有些时候,Java 应用诊断需要参考 OS 相关信息,可使用一些更全面的诊断工具,比如 Zabbix(整合了 OS 和 JVM 监控)等。

在分布式环境中,分布式跟踪系统等基础设施也对应用性能诊断提供了有力支持七、性能优化实践在介绍了一些常用的性能诊断工具后,下面将结合我们在 Java 应用调优中的一些实践,从 JVM 层、应用代码层以及数据库层进行案例分享。

JVM 调优:GC 之痛XX商业平台某系统重构时选择 RMI 作为内部远程调用协议,系统上线后开始出现周期性的服务停止响应,暂停时间由数秒到数十秒不等通过观察 GC 日志,发现服务自启动后每小时会出现一次 Full GC。

由于系统堆设置较大,Full GC 一次暂停应用时间会较长,这对线上实时服务影响较大经过分析,在重构前系统没有出现定期 Full GC 的情况,因此怀疑是 RMI 框架层面的问题通过公开资料,发现 RMI 的 GDC(Distributed Garbage Collection,分布式垃圾收集)会启动守护线程定期执行 Full GC 来回收远程对象,清单 2 中展示了其守护线程代码。

清单 2.DGC 守护线程源代码private static class Daemon extends Thread { public void run() { for (;;) { //… long d = maxObjectInspectionAge(); if (d >= l) { System.gc(); d = 0; } //… } } }

定位问题后解决起来就比较容易了一种是通过增加-XX:+DisableExplicitGC 参数,直接禁用系统 GC 的显示调用,但对使用 NIO 的系统,会有堆外内存溢出的风险另一种方式是通过调大 -Dsun.rmi.dgc.server.gcInterval 和

-Dsun.rmi.dgc.client.gcInterval 参数,增加 Full GC 间隔,同时增加参数-XX:+ExplicitGCInvokesConcurrent,将一次完全 Stop-The-World 的 Full GC 调整为一次并发 GC 周期,减少应用暂停时间,同时对 NIO 应用也不会造成影响。

从图 11 可知,调整之后的 Full GC 次数 在 3 月之后明显减少。

图 11.Full GC 监控统计GC 调优对高并发大数据量交互的应用还是很有必要的,尤其是默认 JVM 参数通常不满足业务需求,需要进行专门调优GC 日志的解读有很多公开的资料,本文不再赘述GC 调优目标基本有三个思路:降低 GC 频率,可以通过增大堆空间,减少不必要对象生成;降低 GC 暂停时间,可以通过减少堆空间,使用 CMS GC 算法实现;避免 Full GC,调整 CMS 触发比例,避免 Promotion Failure 和 Concurrent mode failure(老年代分配更多空间,增加 GC 线程数加快回收速度),减少大对象生成等。

作者:Java架构师追风链接:https://www.jianshu.com/p/40c5a296d896来源:简书

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

河南中青旅行社综合资讯 奇遇综合资讯 盛世蓟州综合资讯 综合资讯 游戏百科综合资讯 新闻4282