运行时性能问题的首要指标之一是来自 JVM 分析器或 JAVA 监控工具的高 Java CPU 使用率报告。但是,windows 和 linux 上的高 Java CPU 利用率问题并不总是容易解决。
例如,如果应用程序过度分配实例,当对象引用超出范围时,垃圾收集器 (GC) 将被迫采取行动。越来越频繁的 GC 周期不仅会触发 JVM stop-the-world 事件,使应用程序看起来没有响应,而且还会导致 Java CPU 使用率飙升。GC 问题的纠正与实现更高效的算法或逻辑工作流无关。解决方法是解决底层对象分配问题,即低效使用内存并触发不必要的 GC。
Java CPU 指标可能会产生误导
具有争用问题的阻塞线程也可能导致 JVM 分析器工具报告 100% 的 Java CPU 利用率。并发问题和死锁并不是真正的处理器问题,而是线程分配方式的问题,以及它们访问的方法是同步的还是阻塞的。
CPU 利用率也可能是一个误导性指标。
当 CPU 处于空闲状态时,它会将其状态报告为未使用,因为它没有做任何工作。但是,当线程被阻塞时,它们会将 CPU 置于等待状态。CPU 处于等待状态时不执行任何逻辑,但它会向 JVM 分析工具报告它很忙,尽管它什么都不做。
此外,不要仅仅因为你的硬件报告了 100% 的 CPU 利用率,就认为是 JVM 导致了它。当你的应用程序处于负载状态时,CPU 使用率可能会飙升,但该峰值可能归因于系统进程或软件堆栈的错误配置。如果服务器的虚拟内存配置错误,页面文件抖动将消耗大部分 CPU 周期。DevOps 团队或系统管理员需要解决虚拟内存问题,这不是由你的应用程序或你如何调整 JVM 性能造成的问题。
最常见的 Java 性能问题
大多数 JVM 性能问题可以追溯到 I/O 操作,例如写入文件系统或与后端关系数据库管理系统或消息队列的交互。配置错误的数据库连接池(其中不断创建和销毁资源以向 Spring 和基于 JPA 的应用程序提供 Java 数据库连接)可能会触发高 Java CPU 使用率。糟糕的 I/O 资源管理也会导致内存泄漏,以及不可避免的 OutOfMemoryError。
另一个导致 Java CPU 使用率高的误导性来源是设计不佳的 RESTful API,它对其他服务进行过多的网络调用。具有大量 HTTP 请求的聊天应用程序,以及在每个请求-响应周期中解析 JSON 和 XML 的相关开销,通常会触发 100% Java CPU 使用率报告。随着开发人员将软件单体重新架构为微服务,这个问题在现代企业架构中变得越来越普遍。
Java CPU 使用率高的外围原因
糟糕的 JVM 内存管理;
Java GC 配置不当;
更正确地归因于软件堆栈的问题;
线程同步、争用和死锁问题;
底层文件和数据库 I/O 问题。
只有在根本原因分析消除了这些问题作为高 Java CPU 使用问题的潜在原因之后,才应该花时间对代码中的潜在问题进行故障排除。
Java CPU 使用率高的直接原因
当你的 Java 代码对 CPU 造成太大压力时,罪魁祸首可能是什么?高 Java CPU 使用率问题的最常见、直接可归因的原因包括:
无限循环
无论是栅栏错误还是草率的开发,程序员开始循环并错误地编码打破它的条件并不是闻所未闻的。结果是一个除了消耗时钟周期之外什么都不做的无限循环。如果有多个线程访问这行代码,那么你的多线程应用程序只会进行无意义的迭代。消除无限循环,CPU 使用应该恢复正常。
写得不好的工作流程和算法
CPU 执行逻辑。如果应用程序包含编写不佳的工作流程,并且代码像意大利面条一样连接在一起,那么你的 CPU 将吞噬不必要的时钟周期。更新常用的工作流程并重新处理性能不佳的算法,以充分利用 CPU。
递归逻辑
虽然一些编程语言针对递归逻辑进行了优化,但 Java 不是其中之一。递归算法创建难以突破的线程,分配不易被垃圾收集算法回收的对象,并且它们创建了难以展开的 Java 堆栈框架塔。考虑到 StackOverflowError 的迫在眉睫的威胁,迭代超过递归算法的情况并不难实现。
选择不当的集合类
列表处理是大多数企业应用程序的核心。因此,开发人员有许多集合类可供选择。如果开发人员在大型数据集上选择使用 LinkedList 而不是 ArrayList,则 CPU 利用率将飙升。同样,如果开发人员选择使用旧的 Hashtable 而不是 HashMap,同步可能会不必要地消耗时钟周期。选择错误的 Java 集合类,应用程序性能将受到影响。选择正确的集合类,你的 Java CPU 使用率高的问题就会消失。
重新计算已计算的值
在整个应用程序中多次计算给定值的情况并不少见。如果是这种情况,请将第一次计算的结果保存在一个变量中,并在以后的所有交互中引用该变量。像这样的小改动会对应用程序性能产生重大影响,尤其是在涉及加密、图形操作或其他 CPU 密集型操作的情况下。
借助 Java Flight Recorder 之类的良好 JVM 分析器以及可用于检查结果的 JDK Mission Control 工具之类的分析工具,确定导致 Java CPU 使用率过高问题的罪魁祸首应该不是问题。一旦确定,找到修复只是实施新的软件例程并测试结果直到修复的问题。