作者:垆皓
前言
随着更多企业迁移上云,应用运行环境、网络发生变化。当应用遇到故障需要问题定位时,一些传统问题定位手段由于效率、准确性等问题已无法满足 SRE 运维需求。本文以问题驱动为视角,结合阿里巴巴自身实践与客户服务经验,完整梳理可观测时代 Java 应用诊断知识图谱。
通过用户回访、工单聚类等技术调研,我们发现应用慢调用、慢 SQL、内存溢出,以及 CPU 使用率高等异常是问题多发区。针对以上常见问题,对比传统故障定位手段,我们将详细解析如何借助自动化、成熟可观测体系实现故障快速定界与定位。
关注阿里云云原生公众号,回复关键词【JAVA全景图】获取高清大图下载地址!
Java 应用诊断-全景图首次发布
常见问题一:如何排查用户态 CPU 使用率高?
jstack 排查 CPU 使用率高
用户态 CPU 使用率反映了应用程序的繁忙程度,通常与业务应用代码实现相关。因此,在进行应用发布、配置变更或性能优化时,想定位消耗 CPU 最多的 Java 代码,可以遵循如下思路:
- 通过 top 命令找到 CPU 消耗最多进程号;
- 通过 top -Hp 进程号 命令找到 CPU 消耗最多线程号(列名仍然为 PID),并转换成 16 进制;
- 通过 jstack 进程号 | grep 16 进制线程号 -A 10 命令找到 CPU 消耗最多线程方法堆栈。
上述方法是目前在定位 CPU 类型问题最常用诊断流程。然而上述方法有两个显著缺陷,一是操作流程复杂且一次 jstack 还不足以定位根因,需要执行多次来进行对比;二是只能用于诊断在线问题,如果问题已经发生并无法复现,没有办法还原问题第一现场,往往只能不了了之。
ARMS诊断思路(线程剖析)
ARMS 为了解决多次 jstack 时机不足以及无法还原第一现场问题,将被动诊断变为主动诊断。通过实时检测、主动收集、在线分析的思路,当发生 CPU 使用率问题时,通过白屏化方式第一时间抓取到应用业务线程堆栈。
常见问题二:如何排查应用 OutOfMemoryError 问题?
当 JVM 内存严重不足时,就会抛出 java.lang.OutOfMemoryError 错误。
Java heap space 空间不足
当堆内存(Heap Space)没有足够空间存放新创建的对象时,就会抛出 java.lang.OutOfMemoryError: Java heap space 错误(根据实际生产经验,可以对程序日志中的 OutOfMemoryError 配置关键字告警,一经发现,立即处理)。
- 原因分析
Java heap space 错误产生的常见原因可以分为以下几类:
- 请求创建一个超大对象,通常是一个大数组。
- 超出预期的访问量/数据量,通常是上游系统请求流量飙升,常见于各类促销/秒杀活动,可以结合业务流量指标排查是否有尖状峰值。
- 过度使用终结器(Finalizer),该对象没有立即被 GC。
- 内存泄漏(Memory Leak),大量对象引用没有释放,JVM 无法对其自动回收,常见于使用了 File 等资源没有回收。
- 解决方案
针对大部分情况,通常只需要通过 -Xmx 参数调高 JVM 堆内存空间即可。如果仍然没有解决,可以参考以下情况做进一步处理:
- 如果是超大对象,可以检查其合理性,比如是否一次性查询了数据库全部结果,而没有做结果数限制。
- 如果是业务峰值压力,可以考虑添加机器资源,或者做限流降级。
- 如果是内存泄漏,需要找到持有的对象,修改代码设计,比如关闭没有释放的连接。
Metaspace 元空间不足
- 原因分析
JDK 1.8 使用 Metaspace 替换了永久代(Permanent Generation),该错误表示 Metaspace 已被用满,通常是因为加载的 class 数目太多或体积太大。
- 解决方案
此类问题的原因与解决方法跟 Permgen space 非常类似,可以参考上文。需要特别注意的是调整 Metaspace 空间大小的启动参数为 -XX:MaxMetaspaceSize。
Unable to create new native thread
每个 Java 线程都占用一定内存空间,当 JVM 向底层操作系统请求创建一个新的 native 线程时,如果没有足够资源分配就会报此类错误。
- 原因分析
JVM 向 OS 请求创建 native 线程失败,就会抛出 Unable to create new native thread,常见的原因包括以下几类:
- 线程数超过操作系统最大线程数 ulimit 限制;
- 线程数超过 kernel.pid_max(只能重启);
- native 内存不足。
该问题发生的常见过程主要包括以下几步:
1. JVM 内部的应用程序请求创建一个新 Java 线程;
2. JVM native 方法代理了该次请求,并向操作系统请求创建一个 native 线程;
3. 操作系统尝试创建一个新 native 线程,并为其分配内存;
4. 如果操作系统的虚拟内存已耗尽,或是受到 32 位进程的地址空间限制,操作系统就会拒绝本次 native 内存分配;
5. JVM 将抛出 java.lang.OutOfMemoryError: Unable to create new native thread 错误。
- 解决方案
- 升级配置,为机器提供更多的内存;
- 降低 Java Heap Space 大小;
- 修复应用程序的线程泄漏问题;
- 限制线程池大小;
- 使用 -Xss 参数减少线程栈的大小;
- 调高 OS 层面的线程最大数:执行 ulimia -a 查看最大线程数限制,使用 ulimit -u xxx 调整最大线程数限制。
ARMS 诊断思路(内存快照分析)
ARMS 提供轻量级内存分析工具,无需登陆到业务机器上执行 jmap 命令,通过白屏化的方式应用出现 OOM 的时候,在线分析内存占用的分布情况,找到占用比重最高的业务代码。
常见问题三:如何排查应用出现的慢调用的问题?
依赖应用日志打点
通常来讲在应用关键代码进行拦截比如通过Spring的AOP,在方法执行的开始以及结束进行规范化的日志输出(包含关键字),当应用出现慢调用请求时,能够通过关键快速找到对应的请求并找到实际的耗时。此种方案所带来的弊端是对于业务代码具有一定的侵入性,并且需要长期来进行维护,当找到具体执行慢的请求时,因执行方法体内部没有打点,所以对于业务复杂的执行过程来讲,比较难直接找到根因。
ARMS 诊断思路(Tracing + 方法堆栈)
通过非侵入探针植入的方式,业务代码零改造,自动具备Tracing的能力,并且能够深入到方法堆栈的级别,找到慢调用请求后,分钟级可实现慢方法问题的根因定位。