共计 6558 个字符,预计需要花费 17 分钟才能阅读完成。
Java 利用性能优化是一个陈词滥调的话题,典型的性能问题如页面响应慢、接口超时,服务器负载高、并发数低,数据库频繁死锁等。尤其是在“糙快猛”的互联网开发模式大行其道的明天,随着零碎访问量的减少和代码的日渐臃肿,各种性能问题开始络绎不绝。Java 利用性能的瓶颈点十分多,比方磁盘、内存、网络 I/O 等零碎因素,Java 利用代码,JVM GC,数据库,缓存等。将 Java 性能优化分为 4 个层级:应用层、数据库层、框架层、JVM 层。
每层优化难度逐级减少,波及的常识和解决的问题也会不同。
- 应用层须要了解代码逻辑,通过 Java 线程栈定位有问题代码行等;
- 数据库层面须要剖析 SQL、定位死锁等;
- 框架层须要懂源代码,了解框架机制;
- JVM 层须要对 GC 的类型和工作机制有深刻理解,对各种 JVM 参数作用了然于胸。
围绕 Java 性能优化,有两种最根本的分析方法:现场分析法和预先分析法。现场分析法通过保留现场,再采纳诊断工具剖析定位。现场剖析对线上影响较大,局部场景(特地是波及到用户要害的在线业务时)不太适合。预先分析法须要尽可能多收集现场数据,而后立刻复原服务,同时针对收集的现场数据进行预先剖析和复现。上面咱们从性能诊断工具登程,分享回顾 HeapDump 性能社区中的一些经典案例与实际。
性能诊断工具
性能诊断一种是针对曾经确定有性能问题的零碎和代码进行诊断,还有一种是对预上线零碎提前性能测试,确定性能是否合乎上线要求。本文次要针对前者,后者能够用各种性能压测工具(例如 JMeter)进行测试,不在本文探讨范畴内。针对 Java 利用,性能诊断工具次要分为两层:OS 层面和 Java 利用层面(包含利用代码诊断和 GC 诊断)。
OS 诊断
OS 的诊断次要关注的是 CPU、Memory、I/O 三个方面。
CPU 诊断
对于 CPU 次要关注均匀负载(Load Average),CPU 使用率,上下文切换次数(Context Switch)。
通过 top 命令能够查看零碎均匀负载和 CPU 使用率。
PerfMa 开源的 XPocket 插件容器中集成了 top_x,它是 linux top 的增强版, 能够显示 CPU 占用率 / 负载,CPU 及内存过程应用的 list。这个插件对于繁冗的 top 命令输入进行了性能的拆分和整顿,更加清晰易用,反对管道化,尤其能够间接拿到 top 过程或线程 tid、pid;。mem_s 命令减少了依照过程 swap 大小占用排序加强了原有 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 会将以后线程被动挂起。简略来说就是生产速度跟不上生产速度的时候,生产线程做了有限重试,重试距离为 1 nano,导致 cpu 频繁挂起唤醒,产生大量 cpu 切换,占用 cpu 资源。把 Distuptor 版本和 og4j2 版本别离到 3.3.6 和 2.7 问题得以解决。
Memory
从操作系统角度,内存关注利用过程是否足够,能够应用 free –m 命令查看内存的应用状况。通过 top 命令能够查看过程应用的虚拟内存 VIRT 和物理内存 RES,依据公式 VIRT = SWAP + RES 能够推算出具体利用应用的替换分区(Swap)状况,应用替换分区过大会影响 Java 利用性能,能够将 swappiness 值调到尽可能小。因为对于 Java 利用来说,占用太多替换分区可能会影响性能,毕竟磁盘性能比内存慢太多。
I/O
I/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 等。此处总结列出了 Linux 不同设施类型的性能诊断工具,如下图所示,可供参考。
Java 利用诊断工具
利用代码诊断
利用代码性能问题是绝对好解决的一类性能问题。通过一些利用层面监控报警,如果确定有问题的性能和代码,间接通过代码就能够定位;或者通过 top+jstack,找出有问题的线程栈,定位到问题线程的代码上,也能够发现问题。对于更简单,逻辑更多的代码段,通过 Stopwatch 打印性能日志往往也能够定位大多数利用代码性能问题。
罕用的 Java 利用诊断包含线程、堆栈、GC 等方面的诊断。
jstack
jstack 命令通常配合 top 应用,通过 top -H -p pid 定位 Java 过程和线程,再利用 jstack -l pid 导出线程栈。因为线程栈是瞬态的,因而须要屡次 dump,个别 3 次 dump,个别每次隔 5s 就行。将 top 定位的 Java 线程 pid 转成 16 进制,失去 Java 线程栈中的 nid,能够找到对应的问题线程栈。
XPocket 中集成了 jstack_x 工具,能够应用 stack -t nid 命令查看某个期待锁线程的调用栈,通过调用栈来定位业务代码。
XElephant、XSheepdog
XElephant 是 HeapDmp 性能社区收费提供的一款在线剖析 Java 内存 Dump 文件的产品。能够让内存里对象之间的各种依赖关系更加清晰明了,无需装置软件,提供上传形式,不受本地机器内存限度,反对超大 Dump 文件剖析。
XSheepdog 是 HeapDmp 性能社区收费提供的一款在线剖析线程 Dump 文件的产品,将线程、线程池、栈、办法及锁的关系梳理分明,通过多种视角呈献给用户,让线程问题高深莫测。
GC 诊断
Java GC 解决了程序员治理内存的危险,但 GC 引起的利用暂停成了另一个须要解决的问题。JDK 提供了一系列工具来定位 GC 问题,比拟罕用的有 jstat、jmap,还有第三方工具 MAT 等。
jstat
jstat 命令可打印 GC 详细信息,Young GC 和 Full GC 次数,堆信息等。其命令格局为
jstat –gcxxx -t pid <interval> <count>。
MAT
MAT 是 Java 堆的剖析利器,提供了直观的诊断报告,内置的 OQL 容许对堆进行类 SQL 查问,功能强大,outgoing reference 和 incoming reference 能够对对象援用追根溯源。
MAT 有两列显示对象大小,别离是 Shallow size 和 Retained size,前者示意对象自身占用内存的大小,不蕴含其援用的对象,后者是对象本人及其间接或间接援用的对象的 Shallow size 之和,即该对象被回收后 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 -gc 1 2s
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
170496.0 170496.0 0.0 0.0 171008.0 130368.9 1024000.0 590052.8 70016.0 68510.8 8064.0 7669.0 983 13.961 1400 275.606 289.567
170496.0 170496.0 0.0 0.0 171008.0 41717.2 1024000.0 758914.9 70016.0 68510.8 8064.0 7669.0 987 14.011 1401 275.722 289.733
170496.0 170496.0 0.0 0.0 171008.0 126547.2 1024000.0 770587.2 70016.0 68510.8 8064.0 7669.0 990 14.091 1403 275.986 290.077
170496.0 170496.0 0.0 0.0 171008.0 45488.7 1024000.0 650767.0 70016.0 68531.9 8064.0 7669.0 994 14.148 1405 276.222 290.371
170496.0 170496.0 0.0 0.0 171008.0 146029.1 1024000.0 714857.2 70016.0 68531.9 8064.0 7669.0 995 14.166 1406 276.366 290.531
170496.0 170496.0 0.0 0.0 171008.0 118073.5 1024000.0 669163.2 70016.0 68531.9 8064.0 7669.0 998 14.226 1408 276.736 290.962
170496.0 170496.0 0.0 0.0 171008.0 3636.1 1024000.0 687630.0 70016.0 68535.6 8064.0 7669.6 1001 14.342 1409 276.871 291.213
170496.0 170496.0 0.0 0.0 171008.0 87247.2 1024000.0 704977.5 70016.0 68535.6 8064.0 7669.6 1005 14.463 1411 277.099 291.562
简直每 1 秒都有一次 FGC,且进展工夫相当长。最初敞开了参数 -XX:-UseAdaptiveSizePolicy,优化后重启服务,访问速度又快起来了。
GC 调优对高并发大数据量交互的利用还是很有必要的,尤其是默认 JVM 参数通常不满足业务需要,须要进行专门调优。GC 日志的解读有很多公开的材料,本文不再赘述。GC 调优指标根本有三个思路:升高 GC 频率,能够通过增大堆空间,缩小不必要对象生成;升高 GC 暂停工夫,能够通过缩小堆空间,应用 CMS GC 算法实现;防止 Full GC,调整 CMS 触发比例,防止 Promotion Failure 和 Concurrent mode failure(老年代调配更多空间,减少 GC 线程数放慢回收速度),缩小大对象生成等。
应用层调优:嗅到坏代码的滋味
从应用层代码调优动手,分析代码效率降落的本源,无疑是进步 Java 利用性能的很好的伎俩之一。
FGC 实战:坏代码导致服务频繁 FGC 无响应问题剖析一文就记录了坏代码导致内存透露 CPU 占用过高大量接口超时的案例。
应用 MAT 工具剖析 jvm Heap,从下面的饼图中能够看出,绝大多数堆内存都被同一个内存占用了,再查看堆内存详情,向下层追溯,很快就发现了罪魁祸首。
找到内存透露的对象了,在我的项目里全局搜寻对象名,它是一个 Bean 对象,而后定位到它的一个类型为 Map 的属性。这个 Map 依据类型用 ArrayList 存储了每次探测接口响应的后果,每次探测完都塞到 ArrayList 里去剖析,因为 Bean 对象不会被回收,这个属性又没有革除逻辑,所以在服务十来天没有上线重启的状况下,这个 Map 越来越大,直至将内存占满。内存满了之后,无奈再给 HTTP 响应后果分配内存了,所以始终卡在 readLine 那。而咱们那个大量 I/O 的接口报警次数特地多,预计跟响应太大须要更多内存无关。
对于坏代码的定位,除了惯例意义上的代码审查外,借助 MAT 等工具也能够在肯定水平对系统性能瓶颈点进行疾速定位。然而一些与特定场景绑定或者业务数据绑定的状况,却须要辅助代码走查、性能检测工具、数据模仿甚至线上引流等形式能力最终确认性能问题的出处。以下是咱们总结的一些坏代码可能的一些特色,供大家参考:
(1)代码可读性差,无根本编程标准;
(2)对象生成过多或生成大对象,内存泄露等;
(3)IO 流操作过多,或者遗记敞开;
(4)数据库操作过多,事务过长;
(5)同步应用的场景谬误;
(6)循环迭代耗时操作等。
数据库层调优:死锁噩梦
对于大部分 Java 利用来说,与数据库进行交互的场景十分广泛,尤其是 OLTP 这种对于数据一致性要求较高的利用,数据库的性能会间接影响到整个利用的性能。
通常来说,对于数据库层的调优咱们基本上会从以下几个方面登程:
(1)在 SQL 语句层面进行优化:慢 SQL 剖析、索引剖析和调优、事务拆分等;
(2)在数据库配置层面进行优化:比方字段设计、调整缓存大小、磁盘 I/O 等数据库参数优化、数据碎片整顿等;
(3)从数据库构造层面进行优化:思考数据库的垂直拆分和程度拆分等;
(4)抉择适合的数据库引擎或者类型适应不同场景,比方思考引入 NoSQL 等。
总结与倡议
性能调优同样遵循 2-8 准则,80% 的性能问题是由 20% 的代码产生的,因而优化要害代码事倍功半。同时,对性能的优化要做到按需优化,适度优化可能引入更多问题。对于 Java 性能优化,不仅要了解零碎架构、利用代码,同样须要关注 JVM 层甚至操作系统底层。总结起来次要能够从以下几点进行思考:
1)根底性能的调优
这里的根底性能指的是硬件层级或者操作系统层级的降级优化,比方网络调优,操作系统版本升级,硬件设施优化等。比方 F5 的应用和 SDD 硬盘的引入,包含新版本 Linux 在 NIO 方面的降级,都能够极大的促成利用的性能晋升;
2)数据库性能优化
包含常见的事务拆分,索引调优,SQL 优化,NoSQL 引入等,比方在事务拆分时引入异步化解决,最终达到一致性等做法的引入,包含在针对具体场景引入的各类 NoSQL 数据库,都能够大大缓解传统数据库在高并发下的有余;
3)利用架构优化
引入一些新的计算或者存储框架,利用新个性解决原有集群计算性能瓶颈等;或者引入分布式策略,在计算和存储进行程度化,包含提前计算预处理等,利用典型的空间换工夫的做法等;都能够在肯定水平上升高零碎负载;
4)业务层面的优化
技术并不是晋升零碎性能的惟一伎俩,在很多呈现性能问题的场景中,其实能够看到很大一部分都是因为非凡的业务场景引起的,如果能在业务上进行躲避或者调整,其实往往是最无效的。