Java 程序 CPU 使用率过高问题定位与修复
问题现象:CPU 负载过高
我们线上的 jenkins 系统,时不时会发生 CPU 负载过高的现象。
CPU 负载过高后,SRE 同学会收到电话告警。
在我们的监控系统中,可以看到,某些时候,CPU 的负载确实会很高,如下图:
问题排查
Jenkins 系统本身是一个 Java 程序,应对 Java 程序导致的 CPU 使用率过高这一问题,GitHub 上有现成的解决方案:show-busy-java-threads。
下载链接如下:
- GitHub:show-busy-java-threads
- Gitee:show-busiest-java-threads
登录上机器,在 CPU 使用率高时候,执行 show-busy-java-threads 脚本:./show-busy-java-threads
。
摘选其中的一些输出如下:
The stack of busy(25.0%) thread(20239/0x4f0f) of java process(248927) of user(jenkins):
"Handling GET /job/jenkins-test-job/api/json from 172.168.1.1 : qtp1641808846-3127" #3127 prio=5 os_prio=0 tid=0x00007f7380014000 nid=0x4f0f runnable [0x00007f722c392000]java.lang.Thread.State: RUNNABLEat java.util.Arrays.copyOfRange(Arrays.java:3664)at java.lang.String.<init>(String.java:207)at java.lang.String.substring(String.java:1933)at net.sf.json.util.JSONTokener.matches(JSONTokener.java:110)at net.sf.json.JSONObject._fromJSONTokener(JSONObject.java:912)at net.sf.json.JSONObject.fromObject(JSONObject.java:156)at net.sf.json.util.JSONTokener.nextValue(JSONTokener.java:348)at net.sf.json.JSONArray._fromJSONTokener(JSONArray.java:1131)at net.sf.json.JSONArray.fromObject(JSONArray.java:125)at net.sf.json.util.JSONTokener.nextValue(JSONTokener.java:351)at net.sf.json.JSONObject._fromJSONTokener(JSONObject.java:955)at net.sf.json.JSONObject.fromObject(JSONObject.java:156)at net.sf.json.util.JSONTokener.nextValue(JSONTokener.java:348)at net.sf.json.JSONObject._fromJSONTokener(JSONObject.java:955)at net.sf.json.JSONObject.fromObject(JSONObject.java:156)at net.sf.json.util.JSONTokener.nextValue(JSONTokener.java:348)at net.sf.json.JSONObject._fromJSONTokener(JSONObject.java:955)at net.sf.json.JSONObject.fromObject(JSONObject.java:156)at net.sf.json.util.JSONTokener.nextValue(JSONTokener.java:348)at net.sf.json.JSONObject._fromJSONTokener(JSONObject.java:955)at net.sf.json.JSONObject._fromString(JSONObject.java:1145)at net.sf.json.JSONObject.fromObject(JSONObject.java:162)at net.sf.json.JSONObject.fromObject(JSONObject.java:132)at sam.Sam.sendRequestReturnJson(Sam.java:517)at sam.Sam.getPermissionByUser(Sam.java:225)at sam.Sam.checkUserPermissionLocal(Sam.java:243)at com.michelin.cio.hudson.plugins.rolestrategy.PermissionCache.getPermissionSam(RoleMap.java:155)at com.michelin.cio.hudson.plugins.rolestrategy.PermissionCache.getPermission(RoleMap.java:106)at com.michelin.cio.hudson.plugins.rolestrategy.RoleMap.hasPermission(RoleMap.java:220)at com.michelin.cio.hudson.plugins.rolestrategy.RoleMap.access$000(RoleMap.java:166)at com.michelin.cio.hudson.plugins.rolestrategy.RoleMap$AclImpl.hasPermission(RoleMap.java:569)at hudson.security.SidACL._hasPermission(SidACL.java:70)
从上面的输出可以看到,25.0% 的 CPU 资源在处理 Handling GET /job/jenkins-test-job/api/json from 172.168.1.1
这个请求。
运维同学根据这个 ip ,定位到发起请求的是某同学 A。这个同学在跑一些定时任务,定时拉取 job 的执行结果。
问题是当我直接访问这个接口:/job/jenkins-test-job/api/json 时,返回并不慢,几乎很快就可以返回。问题应该不是这个接口的问题。
我们接着从 ./show-busy-java-threads
输出往下看:看到其中有问题的调用栈:
at net.sf.json.JSONObject.fromObject(JSONObject.java:132)
at sam.Sam.sendRequestReturnJson(Sam.java:517)
at sam.Sam.getPermissionByUser(Sam.java:225)
at sam.Sam.checkUserPermissionLocal(Sam.java:243)
看起来是这个 Sam 校验用户权限导致的 CPU 使用率过高,而接着看上面的代码 net.sf.json.JSONObject.fromObject
,这个是在做 json 的反序列化。
通常来说,json 的序列化、反序列化都是比较费 CPU 的,更糟糕的是,这里用到的 json 序列化框架是 net.sf.json,而不是 Java 常用的 jackson 和 gson 等。
直觉告诉我,肯定是这个 net.sf.json 反序列化引起的 CPU 使用率过高问题。
备注:
通过跟之前维护 jenkins 的同学了解到,他们基于 role-strategy 插件,重写了 jenkins 权限验证逻辑,用的就是 Sam 权限。翻看 sam 权限插件的代码,确实有用 net.sf.json 做 json 反序列化。
到这里,定位到大概率是 Sam 权限插件的 net.sf.json 反序列化引起的问题。
问题复现
为了验证这个问题,我们拿到 Sam 权限插件的代码。找到出问题的关键代码:
public void getPermissionByUser(String email) {JSONObject params = new JSONObject();params.put("user_email", email);params.put("subsystem_id", SAM_JENKINS_SUM_SYSTEM_ID);JSONObject res = sendRequestReturnJson(URL, "GET", params);if (res.get("success").equals(true)) {cacheUserPermission(params.getString("user_email"), res.getJSONObject("permission").getJSONObject(email).getJSONObject("SERVICE"));}
}public static JSONObject sendRequestReturnJson(String endpoint, String method, JSONObject params) {if (method.equals("POST")) {return JSONObject.fromObject(sendPostRequest(endpoint, params));} else if (method.equals("GET")) {return JSONObject.fromObject(sendGetRequest(endpoint, params));}return new JSONObject();
}
可以看到,这段代码会根据用户邮箱,发送 http 请求调用 Sam 系统,获取用户的权限数据,然后将数据反序列化成 JSONObject,即:
JSONObject.fromObject(sendGetRequest(endpoint, params, token))
在本地,通过复现 A 同学的请求,发现这个请求确实比较慢,而且费 CPU。通过 debug 得知,这个用户返回的 json 数据有 1M 左右,json 反序列化 CPU 打满。
而通过其他用户请求,发现处理很快,返回的 json 数据也比较小。
到这里,确认就是 net.sf.json 框架的反序列化性能问题,引起的 CPU 使用率过高。我们需要替换成其他高性能的 json 序列化框架。
备选有:gson、jackson、fastjson等。fastjson 因为经常出安全漏洞,暂不考虑,我们考虑从 gson、jackson 选择一个。
在选定之前,先对 gson、jackson, 的性能做个基准测试,并与 net.sf.json 做对比。
JMH 基准测试 json 框架性能
Json 框架的性能测试,我们选用 JMH 框架。
JMH 框架是 JDK 官方提供的性能基准测试套件,参考:https://github.com/openjdk/jmh
代码如下:
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import net.sf.json.JSONObject;
import org.apache.commons.io.FileUtils;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.results.format.ResultFormatType;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import org.springframework.util.ResourceUtils;import java.io.File;
import java.io.IOException;
import java.util.concurrent.TimeUnit;@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@State(Scope.Benchmark)
public class JsonBenchmark {@Param({"10", "100", "500"})private int length;private String json;private String email = "a@123.com";private String path = "classpath:sam.json";@Benchmarkpublic void testGson() throws IOException {Gson gson = new Gson();JsonObject root = gson.fromJson(json, JsonObject.class);if (root.getAsJsonObject("success").getAsBoolean()) {JsonObject services = root.get("permission").getAsJsonObject().get(email).getAsJsonObject().get("SERVICE").getAsJsonObject();System.out.println(services.size());}}@Benchmarkpublic void testJackson() throws IOException {ObjectMapper objectMapper = new ObjectMapper();JsonNode root = objectMapper.readTree(json);if (root.get("success").asBoolean()) {JsonNode services = root.get("permission").get(email).get("SERVICE");System.out.println(services.size());}}@Benchmarkpublic void testJsonObject() throws IOException {JSONObject root = JSONObject.fromObject(json);if (root.get("success").equals(true)) {JSONObject services = root.getJSONObject("permission").getJSONObject(email).getJSONObject("SERVICE");System.out.println(services.size());}}@Setuppublic void prepare() throws IOException {File file = ResourceUtils.getFile(path);json = FileUtils.readFileToString(file);}public static void main(String[] args) throws RunnerException {Options options = new OptionsBuilder().include(JsonBenchmark.class.getSimpleName()).forks(1).warmupIterations(5).measurementIterations(2)
// .output("/Users/wxweven/Benchmark.log").result("result.json").resultFormat(ResultFormatType.JSON).build();new Runner(options).run();}
}
测试的结果如下:
Benchmark (length) Mode Cnt Score Error Units
JsonBenchmark.testGson 10 avgt 2 7.979 ms/op
JsonBenchmark.testGson 100 avgt 2 8.958 ms/op
JsonBenchmark.testGson 500 avgt 2 9.975 ms/op
JsonBenchmark.testJackson 10 avgt 2 10.393 ms/op
JsonBenchmark.testJackson 100 avgt 2 12.214 ms/op
JsonBenchmark.testJackson 500 avgt 2 10.548 ms/op
JsonBenchmark.testJsonObject 10 avgt 2 1350.788 ms/op
JsonBenchmark.testJsonObject 100 avgt 2 1350.583 ms/op
JsonBenchmark.testJsonObject 500 avgt 2 1381.046 ms/op
可以看到,gson 和 jackson 性能接近,但是 jsonlib 性能就很差,比另外两个慢 100 多倍。
综合考虑性能、api 易用性等,选定 gson 作为替代方案。
替换成 gson
将之前的代码替换成 gson,代码如下:
public void getPermissionByUser(String email) {JSONObject params = new JSONObject();params.put("user_email", email);params.put("subsystem_id", SAM_JENKINS_SUM_SYSTEM_ID);JsonObject res = sendRequestReturnJsonV2(URL, "GET", params);if (res.get("success").getAsBoolean()) {cacheUserPermission(params.getString("user_email"), res.getAsJsonObject("permission").getAsJsonObject(email).getAsJsonObject("SERVICE"));}}public static JsonObject sendRequestReturnJsonV2(String endpoint, String method, JSONObject params) throws IOException {if (method.equals("POST")) {return GSON.fromJson(sendPostRequest(endpoint, params, token), JsonObject.class);} else if (method.equals("GET")) {return GSON.fromJson(sendGetRequest(endpoint, params, token), JsonObject.class);}return new JsonObject();
}
重新编译权限插件后上线,再次查看 CPU 负载监控,发现 CPU 负载确实降下来了(05/13晚上 0 点左右上线的)。
再次重新编译,问题得到解决。
结束语
这个问题,前前后后花费了不少时间,也困扰了 DevOps 团队比较久,经过大家的齐心协力,总算是把问题给解决了。
这篇文章也是对之前排查、解决问题的一个总结。
同时,也提醒大家,在使用第三方 jar 包的时候,一定要注意该 jar 包有没有性能、安全等问题。如果不确定的话,可以用 JMH 等手段自己测试以下。
我是梅小西,最近在某东南亚电商公司做 DevOps 的相关事情。从本期开始,将陆续分享基于 Jenkins 的 CI/CD 工作流,包括 Jenkins On k8s 等。
如果你对 Java 或者 Jenkins 等感兴趣,欢迎与我联系,微信:wxweven(备注 DevOps)
本文由博客群发一文多发等运营工具平台 OpenWrite 发布
Java 程序 CPU 使用率过高问题定位与修复相关推荐
- Java程序 CPU使用率过高怎么办?
java程序 cup使用率过高,会导致程序运行速度变慢,导致系统崩溃等 原因是多向的.跟内存泄漏,数据库等都有关 参考一些解决方案: 检查是否有死循环. 频繁的GC.或者有大量的线程. 算法太复杂或者 ...
- java程序cpu突然飚高_Java 定位导致CPU飙升的代码过程
线上的一个日志实时输出的程序曾经出过这样一个问题,刚开始上线java程序占用的CPU的资源很少,但是到了整点的时候,CPU直线飙高,直接到达100%根本没有要下降的趋势,唯一的方法只能杀掉它了,后面在 ...
- java程序CPU使用率高可能的原因
1. 前言 在Java并发编程方面,如何在多线程环境中设置合理的线程数,那我们需要了解两个概念: 计算密集型 要进行大量的计算.逻辑判断等操作,消耗CPU资源,比如计算圆周率.对视频进行高清解码等等, ...
- java资源使用效率较高,Java 进程资源使用率较高问题定位
在实际开发过程中,有些 Java 程序在本地或者在服务器上都可以运行的较正常,但是运行较长一段时间后,可能会出现资源占用率较高的情况,例如 CPU 或 内存占用率较高等情况,以至于发生内存溢出,进程假 ...
- Java应用CPU使用率过高排查
1.使用top命令查看使用CPU过高的进程 top top - 14:16:06 up 27 min, 3 users, load average: 0.00, 0.01, 0.02 Tasks: 8 ...
- Java 占用CPU使用率很高的分析
前几天在测试服务器上发现Java进程的CPU使用率暴高,为了分析解决该问题,把过程记录如下: 1. 先找到Java的进程号 Linux下: 用top命令查看所有进程,可以明显看到Java的,因为CPU ...
- java应用cpu使用率过高问题排查
---------------------------------------linux下如何定位代码问题------------------------------- 1.先通过top命令找到消耗c ...
- 线上java程序CPU占用过高问题排查
简要 工作中负责的有一个项目是使用iReport+JasperReport实现的一个打印系统.最近这个线上程序经常无响应,重启后恢复正常,但是时不时还是会出现类似的问题. 最后发现是JasperRep ...
- java程序cpu突然飚高_高频面试题:Java程序占用 CPU 过高怎么排查
这个问题可以说是 Java 面试的高频面试题了,有很多面试官都喜欢问这个问题,问题可能是下面这样的.线上一台服务器 CPU 使用率100% 了,如果你碰到这样的情况,如何排查并找到问题原因? 这就是一 ...
最新文章
- 递归二分查找时间复杂度、空间复杂度和稳定性
- Node+Express+Vue2.x+Mongodb结合muse-ui、less、rem等实现简易博客
- 联想计算机BIOS开启Intel-vx,如何在计算机BIOS或UEFI固件中启用Intel VT-x | MOS86
- mysql php 变量赋值,在MySQL UPDATE(PHP / MySQL)中使用变量
- Written English-书面-现在完成时
- Spring集成JDBC组件开发
- javascript是什么?有哪些特点?
- find_first_of()和 find_last_of()
- Android知识散点
- S3C2440 进行微秒级、毫秒级延时函数
- Shell中变量的单百分号%和双百分号%%的作用
- Win11稳定版安装安卓子系统(WSA)
- vc循序渐进实现仿QQ界面(三):界面调色与控件自绘
- 英语六级高频词汇速记 + 2020-7听力 Day09
- 前端项目更换鼠标样式
- 任务管理器中查看文件被哪个进程占用
- MySQL 安装报错的解决方法
- 渗透测试-2022红队必备工具列表总结​
- openoffice java awt_OpenOffice开发者指南笔记
- uniapp小程序更改swiper指示点样式
热门文章
- python property类
- 视联网PK互动数字电视和IPTV
- Oracle数据库常用函数总结
- Reflex WMS中阶系列9 - Pick Run之前预留托盘号给备货订单
- 【千锋Python2205班10.21笔记-day05-xpath语法和指令系统(一阶段)】
- 蒙特卡洛方法(入门详解)
- 苹果、微软等巨头107道机器学习面试题/贪心学院
- android仿京东商城例子
- 计算机怎么接入外接键盘,笔记本在接入外接键盘后,如何让原来笔记本自 – 手机爱问...
- l20范数最小化求解系数方程_贪婪组稀疏方法(Greedy group sparsity)