Java诊断技术汇总

187 阅读30分钟

背景

随着互联网技术的发展,软件系统更加复杂,用户对于系统的性能要求也越来越高。在这种情况下,如果不能及时发现并解决系统存在的问题,可能会导致用户体验下降,甚至导致重大的业务损失。因此,掌握Java诊断技能,对于每一个Java开发者来说都是必备技能之一。

目的

  1. 性能优化: 通过Java诊断可以找出程序中的性能瓶颈,比如CPU使用率高,内存消耗大,IO等待时间长等,并通过优化代码或调整系统配置提升程序运行效率。
  2. 故障定位: 当软件在运行过程中出现错误或异常时,可以通过Java诊断来定位问题源头,非常有利于快速排查故障,减少业务中断时间。
  3. 系统稳定性提升: 在进行Java诊断过程中,可以找出可能导致系统崩溃或异常的“隐患”,比如内存泄漏,死锁等问题,并及时进行修复,提升系统稳定性。

诊断方法论

一、在不同环境排查问题,有不同的方式

开发环境

如果是在自己的开发环境排查问题,那你几乎可以使用任何自己熟悉的工具来排查,甚至可以进行单步调试。只要问题能重现,排查就不会太困难,最多就是把程序调试到 JDK 或三方类库内部进行分析。

测试环境

如果是在测试环境排查问题,相比开发环境少的是调试,不过你可以使用 JDK 自带的或阿里的Arthas,附加到远程的 JVM 进程排查问题。另外,测试环境允许造数据、造压力模拟我们需要的场景,因此遇到偶发问题时,我们可以尝试去造一些场景让问题更容易出现,方便测试。

生产环境

如果是在生产环境排查问题,往往比较难:一方面,生产环境权限管控严格,一般不允许调试工具从远程附加进程;另一方面,生产环境出现问题要求以恢复为先(回滚版本),难以留出充足的时间去慢慢排查问题。但因为生产环境的流量真实、访问量大、网络权限管控严格、环境复杂,因此更容易出问题,也是出问题最多的环境。

二、问题排查流程

1、信息收集

在生产环境中,问题排查极度依赖于监控、日志和快照等信息。由于问题发生时,生产环境需要尽快恢复,无法保留完整的问题现场进行排查和测试。因此,充足的信息用于了解过去、还原现场就显得至关重要。

  • 日志

需要确保错误和异常信息能被完整的记录到日志中,并保证程序的日志级别是INFO以上。使用合理的日志优先级是非常必要的,例如DEBUG用于开发调试、INFO用于记录重要流程信息、WARN用于记录需要关注的问题、ERROR用于记录会阻断流程的错误。

  • 监控

在生产环境中,开发和运维团队需要准备充足且多层次的监控。如CPU、内存、磁盘和网络等资源的主机层面监控;应用部署在虚拟机或 Kubernetes 集群中的监控;专线带宽、交换机基本情况、网络延迟等网络层面的监控;以及存储和中间件的内部指标监控等。例如,使用Prometheus这样的著名监控工具,它为各个中间件和存储系统提供了大量的exporter来获取监控数据。在应用层面,需要监控的内容不仅包括JVM进程的类加载、内存、GC、线程等常见指标,还需要确保能够收集并保存应用日志、GC日志。

  • 快照

快照是指应用进程在某一时刻的状态。为生产环境的Java应用设置适当的JVM参数,如 -XX:+HeapDumpOnOutOfMemoryError 和 -XX:HeapDumpPath=…,可以在出现OOM错误时抓取堆快照进行分析。

2、问题排查策略

  • 确定问题层次

在问题定位初期,首先应确认问题出现的层次,即问题是源于Java应用程序本身,还是由外部因素引起。

  • 捕获异常信息:

对于那些会产生异常信息的问题,可以通过查看异常信息快速定位问题。异常信息通常具有描述性,可以直接指向问题所在。

  • 监控和排查资源消耗:

与此同时,对于那些不产生异常但是会消耗系统资源的问题,如 CPU 或内存使用过高等,可以通过综合指标监控与观察现象进行定位。这就需要我们密切关注系统的资源使用情况,包括CPU、内存、硬盘IO以及网络等方面。

  • 程序自身的Bug

这类问题通常在程序发布后立即出现,问题解决的常见策略是回滚到之前的版本,然后在不影响应用运行的情况下进行版本差异分析。

  • 外部因素导致的问题

这类问题通常涉及到主机、中间件和数据库等因素。其中包括:

  1. 主机层面的问题: 可以使用各种系统工具进行排查,如topvmstatpidstatps等用于排查CPU问题;freetoppsvmstatcachestatsar等用于排查内存问题;lsofiostatpidstatsariotopdfdu等用于排查IO问题;ifconfigipnslookupdigpingtcpdumpiptables等用于排查网络问题。
  2. 中间件或存储(统称组件)的问题: 主要通过查看组件所在主机是否存在问题、观察组件进程和各种监控指标、查看组件的日志输出、控制台命令查看等方式进行排查。
  • 系统资源不够导致的问题

这类问题表现为系统假死,初步处理策略通常是先进行重启和扩容以解决现有问题,然后进行具体问题分析。对于此类问题,可以根据CPU使用高、内存泄漏或OOM的问题、IO问题、网络相关问题等方面进行分析。

三、九点心得

1、考虑“鸡”和“蛋”的问题

比如,发现业务逻辑执行很慢且线程数增多的情况时,我们需要考虑两种可能性:

一是,程序逻辑有问题或外部依赖慢,使得业务逻辑执行慢,在访问量不变的情况下需要更多的线程数来应对。比如,10TPS 的并发原先一次请求 1s 可以执行完成,10 个线程可以支撑;现在执行完成需要 10s,那就需要 100 个线程。

二是,有可能是请求量增大了,使得线程数增多,应用本身的 CPU 资源不足,再加上上下文切换问题导致处理变慢了。出现问题的时候,我们需要结合内部表现和入口流量一起看,确认这里的“慢”到底是根因还是结果。

2、考虑通过分类寻找规律

在定位问题没有头绪的时候,我们可以尝试总结规律。

比如,我们有 10 台应用服务器做负载均衡,出问题时可以通过日志分析是否是均匀分布的,还是问题都出现在 1 台机器。又比如,应用日志一般会记录线程名称,出问题时我们可以分析日志是否集中在某一类线程上。再比如,如果发现应用开启了大量 TCP 连接,通过 netstat 我们可以分析出主要集中连接到哪个服务。如果能总结出规律,很可能就找到了突破点。

3、分析问题需要根据调用拓扑来,不能想当然

比如,我们看到 Nginx 返回 502 错误,一般可以认为是下游服务的问题导致网关无法完成请求转发。对于下游服务,不能想当然就认为是我们的 Java 程序,比如在拓扑上可能 Nginx 代理的是 Kubernetes 的 Traefik Ingress,链路是 Nginx->Traefik-> 应用,如果一味排查 Java 程序的健康情况,那么始终不会找到根因。又比如,我们虽然使用了 Spring Cloud Feign 来进行服务调用,出现连接超时也不一定就是服务端的问题,有可能是客户端通过 URL 来调用服务端,并不是通过 Eureka 的服务发现实现的客户端负载均衡。换句话说,客户端连接的是 Nginx 代理而不是直接连接应用,客户端连接服务出现的超时,其实是 Nginx 代理宕机所致。

4、考虑资源限制类问题

观察各种曲线指标,如果发现曲线慢慢上升然后稳定在一个水平线上,那么一般就是资源达到了限制或瓶颈。比如,在观察网络带宽曲线的时候,如果发现带宽上升到 120MB 左右不动了,那么很可能就是打满了 1GB 的网卡或传输带宽。又比如,观察到数据库活跃连接数上升到 10 个就不动了,那么很可能是连接池打满了。观察监控一旦看到任何这样的曲线,都要引起重视。

5、考虑资源相互影响

CPU、内存、IO 和网络,这四类资源就像人的五脏六腑,是相辅相成的,一个资源出现了明显的瓶颈,很可能会引起其他资源的连锁反应。比如,内存泄露后对象无法回收会造成大量 Full GC,此时 CPU 会大量消耗在 GC 上从而引起 CPU 使用增加。又比如,我们经常会把数据缓存在内存队列中进行异步 IO 处理,网络或磁盘出现问题时,就很可能会引起内存的暴涨。因此,出问题的时候,我们要考虑到这一点,以避免误判。

6、排查网络问题要考虑三个方面,到底是客户端问题,还是服务端问题,还是传输问题。

比如,出现数据库访问慢的现象,可能是客户端的原因,连接池不够导致连接获取慢、GC 停顿、CPU 占满等;也可能是传输环节的问题,包括光纤、防火墙、路由表设置等问题;也可能是真正的服务端问题,需要逐一排查来进行区分。服务端慢一般可以看到 MySQL 出慢日志,传输慢一般可以通过 ping 来简单定位,排除了这两个可能,并且仅仅是部分客户端出现访问慢的情况,就需要怀疑是客户端本身的问题。对于第三方系统、服务或存储访问出现慢的情况,不能完全假设是服务端的问题。

7、快照类工具和趋势类工具需要结合使用

比如,jstat、top、各种监控曲线是趋势类工具,可以让我们观察各个指标的变化情况,定位大概的问题点;而 jstack 和分析堆快照的 MAT 是快照类工具,用于详细分析某一时刻应用程序某一个点的细节。一般情况下,我们会先使用趋势类工具来总结规律,再使用快照类工具来分析问题。如果反过来可能就会误判,因为快照类工具反映的只是一个瞬间程序的情况,不能仅仅通过分析单一快照得出结论,如果缺少趋势类工具的帮助,那至少也要提取多个快照来对比。

8、不要轻易怀疑监控

我曾看过一个空难事故的分析,飞行员在空中发现仪表显示飞机所有油箱都处于缺油的状态,他第一时间的怀疑是油表出现故障了,始终不愿意相信是真的缺油,结果飞行不久后引擎就断油熄火了。同样地,在应用出现问题时,我们会查看各种监控系统,但有些时候我们宁愿相信自己的经验,也不相信监控图表的显示。这可能会导致我们完全朝着错误的方向来排查问题。如果你真的怀疑是监控系统有问题,可以看一下这套监控系统对于不出问题的应用显示是否正常,如果正常那就应该相信监控而不是自己的经验。

9、如果因为监控缺失等原因无法定位到根因的话,相同问题就有再出现的风险

需要做好三项工作:做好日志、监控和快照补漏工作,下次遇到问题时可以定位根因;针对问题的症状做好实时报警,确保出现问题后可以第一时间发现;考虑做一套热备的方案,出现问题后可以第一时间切换到热备系统快速解决问题,同时又可以保留老系统的现场。

诊断工具

一、JDK自带的工具

参考文档:docs.oracle.com/en/java/jav…

JDK 自带了很多命令行甚至是图形界面工具,帮助我们查看 JVM 的一些信息,如下:

1、jps

jps用于显示当前应用的进程id,其他的命令都依赖进程id

2、jstack

jstack命令可以用来打印目标 Java 进程中各个线程的栈轨迹,以及这些线程所持有的锁。jstack的其中一个应用场景便是死锁检测。这里我用jstack获取一个已经死锁了的 Java 程序的栈信息。具体输出如下所示:

暂时无法在飞书文档外展示此内容

暂时无法在飞书文档外展示此内容

我们可以看到,jstack不仅会打印线程的栈轨迹、线程状态(BLOCKED)、持有的锁(locked …)以及正在请求的锁(waiting to lock …),而且还会分析出具体的死锁。

常用的命令:jstack pid -l > 其他文件

3、jmap

  • 生成堆存储快照jmap可以生成堆转储文件(heap dump file),它包含了关于Java堆内存中的对象的详细信息,包括每个对象的类、字段、以及值。这个堆转储文件可以被分析工具用于寻找内存泄漏和其他内存相关问题。
  • 打印堆内存详细配置jmap也可以提供有关堆内存使用情况的详细信息,包括例如内存区域的大小(如新生代、老年代、元数据区等)、已用空间以及还剩多少空间等信息。

暂时无法在飞书文档外展示此内容

  • 统计堆内存中的对象:还可以统计堆内存内各对象的数量,包括它们的类名、数量、占用空间信息。这对于找出哪些类型的对象占用了堆中的大部分空间是非常有帮助的。

常用的使用命令:jmap -dump:format=b,file=filename.hprof pid > 其他文件

4、jstat

jstat命令可用来打印目标 Java 进程的性能数据。它包括多条子命令,如下所示:

暂时无法在飞书文档外展示此内容

在这些子命令中,-class将打印类加载相关的数据,-compiler和-printcompilation将打印即时编译相关的数据。剩下的都是以-gc为前缀的子命令,它们将打印垃圾回收相关的数据。

-gccapacity

暂时无法在飞书文档外展示此内容

这条命令会显示覆盖整个堆内存的各个区域的内存使用情况,包括:

  • NGC:新生代(Young Generation)的总大小
  • OGC:旧生代(Old Generation)的总大小
  • S0C:Survivor 0空间的总大小
  • S1C:Survivor 1空间的总大小
  • EC:Eden空间的总大小
  • OGCMN:老生代最小值
  • OGCMX:老生代最大值
  • OC:老生代当前值
  • YGC:新生代垃圾回收次数
  • YGCT:新生代垃圾回收消耗时间
  • FGC:全内存垃圾回收次数(包含新生代和老生代)
  • FGCT:全内存垃圾回收消耗时间
  • GCT:垃圾回收消耗的总时间

请注意,以上所有的数值都是以KB为单位的。

-gcutil

默认情况下,jstat只会打印一次性能数据。如果希望看到 GC 趋势的话,我们可以将它配置为每隔一段时间打印一次,直至目标 Java 进程终止,或者达到我们所配置的最大打印次数。jstat 工具允许以固定的监控频次输出 JVM 的各种监控指标,比如使用 -gcutil 输出 GC 和内存占用汇总信息,23940为进程号pid,每隔 5 秒输出一次,输出 100 次,可以看到 Young GC 比较频繁,而 Full GC 基本 10 秒一次:

暂时无法在飞书文档外展示此内容

其中,S0 表示 Survivor0 区占用百分比,S1 表示 Survivor1 区占用百分比,E 表示 Eden 区占用百分比,O 表示老年代占用百分比,M 表示元数据区占用百分比,YGC 表示年轻代回收次数,YGCT 表示年轻代回收耗时,FGC 表示老年代回收次数,FGCT 表示老年代回收耗时。YGCT和FGCT单位为秒,关注增量的差值

jstat 命令的参数众多,包含 -class、-compiler、-gc 等。Java 8、Linux/Unix 平台 jstat 工具的完整介绍,你可以查看这里。jstat 定时输出的特性,可以方便我们持续观察程序的各项指标

二、MAT内存分析工具

使用jmap工具dump出的文件,或者应用崩溃OOM后生成的文件,可以使用mat工具分析,官网下载地址:eclipse.dev/mat/,运行MAT需…

使用 MAT 分析 OOM 问题,一般可以按照以下思路进行:

  • 通过支配树功能或直方图功能查看消耗内存最大的类型,来分析内存泄露的大概原因;
  • 查看那些消耗内存最大的类型、详细的对象明细列表,以及它们的引用链,来定位内存泄露的具体点;
  • 配合查看对象属性的功能,可以脱离源码看到对象的各种属性的值和依赖关系,帮助我们理清程序逻辑和参数;
  • 辅助使用查看线程栈来看 OOM 问题是否和过多线程有关,甚至可以在线程栈看到 OOM 最后一刻出现异常的线程。

1、概览

2、直方图

3、支配树

4、未可达对象

排查问题时,需要关注未可达对象,由于一些对象不可达,导致无法垃圾回收,进而导致内存泄露

三、Arthas

Arthas是阿里开源的 Java 诊断工具,相比 JDK 内置的诊断工具,要更人性化,并且功能强大,可以实现许多问题的一键定位,而且可以一键反编译类查看源码,甚至是直接进行生产代码热修复,实现在一个工具内快速定位和修复问题的一站式服务。

具体参考文档:arthas操作使用

四、idea自带的插件Profiler

五、第三方工具

1、heapdump性能社区

国内一家针对性能方面问题的社区网站,免费使用,需要注册账号,可以对threaddump、heapdump以及jvm参数,进行分析,直观展示信息,便于方便快速排查定位问题。

暂时无法在飞书文档外展示此内容

2、fastthread

暂时无法在飞书文档外展示此内容

是国外的一个网站,专用于java线程转储文件的分析,每个用户每月有5次使用限制

暂时无法在飞书文档外展示此内容

诊断工具对比

一、内存快照分析

工具优势劣势
MAT- 它是一个功能强大、专业的Java堆分析工具,可以用来分析heapdump文件,找出内存泄漏的原因,定位泄漏的对象,具有快速、准确的划分Java堆和内存泄漏检测功能。
  • 它可以显示内存中对象的大小和保留集,这对于找出哪部分代码使用了大量内存非常有用。 | - 用户界面可能对初学者来说不太友好,需一定的学习曲线。
  • 对于非常大的堆转储,可能需要较大的内存和更长的处理时间。 | | idea插件Profiler(推荐) | - 不需要离开IDE就可以进行内存分析,十分方便。
  • 集成度高,可以方便的分析代码、堆栈等。 | - 需要在IDEA下运行,不能独立使用。
  • 对于大型项目,可能会影响IDE的性能 | | heapdump社区(推荐) | - 用户界面友好,功能全面,易于使用 | - 对于非常大的堆转储,网速不好的情况下,上传需要较长时间 |

二、线程快照分析

工具优势劣势
fastthread- FastThread 提供有一个简单易用的界面来分析Java线程转储。它可以极速选择转储在高 CPU,死锁,线程竞走,过多消耗内存等问题的线程。
  • 有助于快速分析、诊断和解决性能问题,特别是在极端负载的情况下。
  • 它可以生成易懂的图形化报告,让你一目了然地看到线程堆栈的状态。 | - 需要将线程转储上传到在线服务器进行分析,可能存在数据安全性问题。
  • 分析结果的深度和精度可能与专业级的线程分析工具相比稍有不足 | | heapdump性能社区(推荐) | - 用户界面友好,功能全面,易于使用 | - 对于非常大的堆转储,网速不好的情况下,上传需要较长时间 |

常见Java性能问题和优化

一、内存泄漏

程序中已经分配的堆内存,在不再需要时没有被释放,连续出现内存泄漏会导致内存空间被耗尽,严重影响程序的性能。在java中,内存泄露通常是由于长生命周期的对象持有短生命周期对象的引用,导致短生命周期的对象无法被垃圾回收。Java虽然具有自己的垃圾回收机制,但如果对象不再需要时被其他对象持久持有,垃圾收集器就无法回收这些对象,结果就是内存泄露。如果内存泄露长时间持续,可能造成应用程序的性能下降,甚至引OutOfMemoryError错误。

  1. 对象引用管理: 当一个对象不再需要使用时,应该移除所有的引用指向该对象。若是集合对象,在移除元素时,应该使用清除方法(如 list.clear() )清除对象的引用,不仅仅是让集合指向null。
  2. 使用弱引用或软引用:通过java.lang.ref包下的WeakReference或者SoftReference,可以在内存充足的时候保持引用,内存不足的时候释放引用,避免内存泄露。
  3. 资源使用完后及时关闭:如数据库连接、I/O流等在使用完后应及时调用其close()方法关闭,避免资源占用而导致的内存泄漏。
  4. 内存监控工具使用:内存泄漏很难被发现,因此我们需要使用诸如VisualVM、MAT(Memory Analyzer Tools)、JProfile等内存分析工具对运行时的内存进行监控和泄露点的查找。
  5. 谨慎使用静态变量和单例类:静态变量的生命周期跟随类的存在而存在,单例类在内存中也只存在一个实例。如果这些变量或者类引用了其他的对象,而且没有被释放,那么这些对象就会一直存在于内存中,不能被垃圾回收器进行回收。
  6. 避免内部和外部对象之间的交叉引用:这是因为内部类持有一个对外部类对象的隐式引用,如果外部类对象持有内部类对象的显式引用,就会形成一个交叉引用闭环,导致两个对象都无法被回收。使用时需要谨慎处理,避免形成对象回收的障碍。

二、同步问题

在Java多线程编程中,死锁、活锁、饥饿等同步问题都会导致Java应用程序性能下降。例如,死锁会导致两个或多个线程永远等待共享资源,阻塞程序的执行,使得CPU资源浪费。

  1. 避免锁的粗粒度:尽可能地缩小锁定的对象和范围。锁定的代码块太大,可以会导致线程阻塞的时间过长。
  2. 避免多线程共享实例变量:如果可以的话,尽量避免多个线程共享同一个实例变量,或者保证只有不可变对象被多个线程共享。
  3. 使用高级并发库:Java 5 引入了java.util.concurrent包,提供了许多高级的并发工具,如CountDownLatch,CyclicBarrier,BlockingQueue,ConcurrentHashMap等, 这些工具大大简化了并发编程的复杂性。尽可能减少synchronized,wait,notify的直接使用。
  4. 使用线程池:创建和销毁线程是一个昂贵的操作,可以使用线程池来回收和复用线程资源。
  5. 避免死锁:对多个对象加锁时,尽量固定锁的顺序,避免形成循环等待导致的死锁。使用中断和超时策略,检测并中断可能的死锁线程。
  6. 使用Atomic类:尽量使用java.util.concurrent.atomic包下的原子类,并优先考虑对性能有改善的无锁数据结构。
  7. 减少线程上下文切换:上下文切换是消耗CPU资源的一项主要操作。减少不必要的线程切换可以提高性能。
  8. 合理地使用wait和notify/notifyAll:在实现同步时,应尽可能地避免使用低级的wait/notify机制,应优先考虑循环的使用await()/signal()等。

三、不规范的编码

Java应用程序的性能瓶颈通常是由一些耗时的操作或者低效的代码导致的,例如复杂的循环、不必要的计算、夹在I/O操作中的CPU密集型计算等。这些操作或代码可能会引起应用程序运行缓慢,导致性能下降。

  1. 优化数据结构和算法:选择合适的数据结构和算法可以显著提升程序的性能。例如,根据需要选择ArrayList或者LinkedList,根据数据量选择QuickSort或者MergeSort等。
  2. 使用并行和异步处理:在可行的情况下,尽量使用并行和异步处理来减少阻塞和提高程序执行速度。比如使用CompletableFuture,利用多核处理器能力。
  3. 避免阻塞:减小锁的粒度,选择适当的并发数据结构,避免死锁和活锁等。
  4. I/O 操作优化:通过缓冲输入输出流或者使用NIO来提升I/O性能。
  5. 使用适当的设计模式:设计模式如工厂模式、单例模式、观察者模式等,都能提升代码的灵活性和可维护性,从而提升应用程序的性能。
  6. 减少对象创建:减少不必要的对象创建可以减少GC的压力,提高性能。对于经常使用的对象,可以通过对象池来重用对象。

四、网络延迟

网络延迟过高或者频繁的网络请求都可能导致Java应用程序的性能下降。例如,在不同的地理位置部署的微服务之间进行通讯,网络延迟就会成为主要的性能瓶颈。

  1. 使用更快的网络或硬件:如果设备和网络的带宽、速度等硬件条件差,网络延迟的问题就比较明显。可以优化的方式有:升级硬件,优化网络环境,选择更佳的网络供应商。
  2. 减少网络请求次数:在设计系统时,尽可能地减少网络请求的次数。比如可以使用批量处理或聚合数据的方式,减少小请求的数量,从而减少网络延迟。
  3. 使用异步和并发处理:在处理网络请求时,尽可能地使用异步和并发的方式来提高系统的吞吐量,降低响应时间,提高系统的性能。例如使用Java的Future、CompletableFuture等工具实现异步调用,使用线程池处理多任务等。
  4. 连接池化:使用连接池可以复用已创建的网络连接,减少了创建和关闭连接的开销,提高了效率。如常见的数据库连接池、HTTP连接池等。
  5. 使用高效的序列化和传输协议:如Google的Protobuf、gRPC等,它们比传统的JSON、XML等格式更加简洁,解析效率更高,可以降低网络传输的开销。
  6. 优化网络配置:针对网络设置的一些参数,如TCP的窗口缩放、TCP的快速打开等,根据实际网络环境进行合理配置。

五、数据库操作问题

对数据库的操作是造成应用性能瓶颈的常见原因。慢查询或者过频的数据库请求都会不必要地消耗CPU和内存资源,影响应用程序的性能。

  1. 优化SQL查询:查询语句的效率直接影响着数据库的性能。分析和理解SQL执行计划,避免不走设置的索引而进行全表扫描,可以帮助你理解查询是如何执行的,对于优化复杂查询非常有用。
  2. 使用索引:为表字段建立合适的索引可以加快查询速度。需要注意的是,索引并不是越多越好。首先,索引需要占用磁盘空间,其次,当进行插入、更新和删除操作时,索引也需要被更新,过多的索引会降低这些操作的性能。
  3. 尽可能减少数据库连接的建立和销毁的次数:每次建立和销毁数据库连接都会进行一系列复杂的操作,消耗系统的CPU和内存资源。可以使用数据库连接池的方式来复用已经建立的连接。
  4. 避免N+1查询问题:在使用ORM框架时,不正确的使用方式会导致N+1查询问题。称为"懒加载"的特性就是N+1查询问题的主要来源,尽可能通过像"延迟加载"这样的方式解决。
  5. 合理设置数据库的参数:根据系统的实际情况,合理地设置数据库的参数,例如内存的大小、连接数的数量等。
  6. 将读写分离:在并发量大的情况下,为了提升数据库的处理性能,可以考虑将读写操作进行分离。
  7. 采用缓存技术:使用缓存可以保存数据库查询的结果,当下次进行相同的查询时,可以直接从缓存中获取,而不需要再次执行查询操作

七、垃圾收集过频(内存碎片)

如果频繁地创建和销毁对象,尤其是创建大量的小对象,可能会导致Java堆内存出现碎片化。当内存碎片化严重时,即使堆内存总体可用,也可能因为没有合适的连续内存块来存放新创建的对象,从而触发OutOfMemoryError。

  1. 选择适当的垃圾收集器:Java有多种垃圾收集器可供选择,包括Serial、Parallel、CMS、G1等。不同的垃圾收集器有不同的特点和使用场景,选择合适的垃圾收集器可以较大程度上改善GC性能。
  2. 优化堆内存分配:配置适当大小的新生代和老年代空间可以减少GC的频率和停顿时间。
  3. 减少暂时对象的创建:创建大量短生命周期的临时对象,会加大垃圾收集的压力。应尽可能复用对象,减少对象的创建和销毁。
  4. 使用对象池:频繁创建和销毁对象可能导致大量的垃圾产生和垃圾收集,使用对象池可以减少这种情况。

问题场景案例操作

一、单个接口RT超时

使用arthas工具的trace命令,查看接口方法调用链,定位耗时长的方法,针对问题方法进行优化

暂时无法在飞书文档外展示此内容

arthas其他命令使用:arthas操作使用

二、CPU飙高,load高,响应很慢

  1、一个请求过程中多次thread dump;

  2、对比多次threaddump文件的runnable线程,如果执行的方法有比较大变化,说明比较正常。如果在执行同一个方法,就有一些问题了;

三、查找占用CPU最多的线程

  1、使用命令:top -H -p pid(pid为被测系统的进程号),找到导致CPU高的线程ID,对应thread dump信息中线程的nid,只不过一个是十进制,一个是十六进制;

暂时无法在飞书文档外展示此内容

  2、在thread dump中,根据top命令查找的线程id,查找对应的线程堆栈信息;

四、CPU使用率不高但是响应很慢

进行threaddump,查看是否有很多thread struck在了i/o、数据库等地方,定位瓶颈原因;

五、请求无法响应,查看是否存在死锁

1、多次threaddump,对比是否所有的runnable线程都一直在执行相同的方法,如果是的,可能是有死锁。

2、使用arthas,执行thread -b命令,查看是否存在死锁

六、系统整体响应超时

  1. 查看应用服务是否正常

确定是java应用服务自身的问题,还是网络或其他问题

  1. 查看日志

确定应用服务是否有异常日志

  1. 查看监控

CPU、内存等其他指标是否正常

  1. 确定影响用户的范围

考虑是否立即回滚、保留现场后回滚

  1. 进入应用服务容器,进行线程、堆内存的dump

由于容器中目录/data/dump,挂载了对应的pvc,所以使用jmap、jstack命令导出的文件,都转储到/data/dump下

暂时无法在飞书文档外展示此内容

6、拷贝快照文件

线上生产环境,需要找运维执行mount挂载命令后,进行拷贝

7、登录跳板机控制台,下载快照文件

8、使用heapdump社区或idea插件Jprofiler,分析快照文件

heapdump性能社区(支持线程快照分析、堆内存快照分析)console.heapdump.cn/,需要走网络上传到服务…

Jprofiler:堆内存分析工具,将快照文件拖入idea,不需要上传网络,直接解析,速度较快

参考资料

极客时间:time.geekbang.org/column/arti…

极客时间:time.geekbang.org/column/arti…

极客时间:time.geekbang.org/column/arti…

华为云社区:bbs.huaweicloud.com/blogs/38738…

HeapDump性能社区:heapdump.cn/

CPU占用100%排查过程:zhuanlan.zhihu.com/p/351514287