java单线程100%利用率
容器内就获取个cpu利用率,怎么就占用单核100%了呢
背景:这个是在centos7 + lxcfs 和jdk11 的环境上复现的
目前这个bug已经合入到了开源社区,
链接为 https://github.com/openjdk/jdk/pull/4378
下面列一下我们是怎么排查并解这个问题的。
一、故障现象
oppo内核团队接到jvm的兄弟甘兄发来的一个案例,
现象是java的热点一直是如下函数,占比很高:
at sun.management.OperatingSystemImpl.getProcessCpuLoad(Native Method)
我们授权后登陆oppo云宿主,发现top显示如下:
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
617248 service 20 0 11.9g 8.6g 9300 R 93.8 2.3 3272:09 java
179526 service 20 0 11.9g 8.6g 9300 R 93.8 2.3 77462:17 java
二、故障现象分析
java的线程,能把cpu使用率打这么高,常见一般是gc或者跑jni的死循环,
从jvm团队反馈的热点看,这个线程应该在一直获取cpu利用率,而不是以前常见的问题。
从perf的角度看,该线程一直在触发read调用。
然后strace查看read的返回值,打印如下:
read(360, 0x7f6f9000b000, 4096) = -1 ENOTCONN (Transport endpoint is not connected)
read(360, 0x7f6f9000b000, 4096) = -1 ENOTCONN (Transport endpoint is not connected)
read(360, 0x7f6f9000b000, 4096) = -1 ENOTCONN (Transport endpoint is not connected)
read(360, 0x7f6f9000b000, 4096) = -1 ENOTCONN (Transport endpoint is not connected)
read(360, 0x7f6f9000b000, 4096) = -1 ENOTCONN (Transport endpoint is not connected)
read(360, 0x7f6f9000b000, 4096) = -1 ENOTCONN (Transport endpoint is not connected)
接着查看360这个fd到底是什么类型:
# ll /proc/22345/fd/360
lr-x------ 1 service service 64 Feb 4 11:24 /proc/22345/fd/360 -> /proc/stat
发现是在读取/proc/stat ,和上面的java热点读取cpu利用率是能吻合的。
根据/proc/stat在容器内的挂载点,
# cat /proc/mounts |grep stat
tmpfs /proc/timer_stats tmpfs rw,nosuid,size=65536k,mode=755 0 0
lxcfs /proc/stat fuse.lxcfs rw,nosuid,nodev,relatime,user_id=0,group_id=0,allow_other 0 0
lxcfs /proc/stat fuse.lxcfs rw,nosuid,nodev,relatime,user_id=0,group_id=0,allow_other 0 0
lxcfs /proc/diskstats fuse.lxcfs rw,nosuid,nodev,relatime,user_id=0,group_id=0,allow_other 0 0
很奇怪的是,发现有两个挂载点都是/proc/stat,
尝试手工读取一下这个文件:
# cat /proc/stat
cpu 12236554 0 0 9808893 0 0 0 0 0 0
cpu0 10915814 0 0 20934 0 0 0 0 0 0
cpu1 1320740 0 0 9787959 0 0 0 0 0 0
说明bash访问这个挂载点没有问题,难道bash访问的挂载点和出问题的java线程访问的挂载点不是同一个?
查看对应java打开的fd的super_block和mount,
crash> files 22345 |grep -w 360
360 ffff914f93efb400 ffff91541a16d440 ffff9154c7e29500 REG /rootfs/proc/stat/proc/stat
crash> inode.i_sb ffff9154c7e29500
i_sb = 0xffff9158797bf000
然后查看对应进程归属mount的namespace中的挂载点信息:
crash> mount -n 22345 |grep lxcfs
ffff91518f28b000 ffff9158797bf000 fuse lxcfs /rootfs/proc/stat
ffff911fb0209800 ffff914b3668e800 fuse lxcfs /rootfs/var/lib/baymax/var/lib/baymax/lxcfs
ffff915535becc00 ffff914b3668e800 fuse lxcfs /rootfs/proc/cpuinfo
ffff915876b45380 ffff914b3668e800 fuse lxcfs /rootfs/proc/meminfo
ffff912415b56e80 ffff914b3668e800 fuse lxcfs /rootfs/proc/uptime
ffff91558260f300 ffff914b3668e800 fuse lxcfs /rootfs/proc/stat/proc/stat
ffff911f52a6bc00 ffff914b3668e800 fuse lxcfs /rootfs/proc/diskstats
ffff914d24617180 ffff914b3668e800 fuse lxcfs /rootfs/proc/swaps
ffff911de87d0180 ffff914b3668e800 fuse lxcfs /rootfs/sys/devices/system/cpu/online
由于cat操作较短,写一个简单的demo代码,open 之后不关闭并且read 对应的/proc/stat路径,发现它
访问的挂载点的super_blcok是 ffff914b3668e800,由于代码极为简单,在此不列出。
也就是进一步地确认了,出问题的java进程访问的是一个出问题的挂载点。
那为什么访问这个挂载点会返回ENOTCONN呢?
crash> struct file.private_data ffff914f93efb400
private_data = 0xffff911e57b49b80---根据fuse模块代码,这个是一个fuse_file
crash> fuse_file.fc 0xffff911e57b49b80
fc = 0xffff9158797be000
crash> fuse_conn.connected 0xffff9158797be000
connected = 0
果然对应的fuse_conn的连接状态是0,从打点看,是在fuse_get_req返回的:
调用链如下:
sys_read-->vfs_read-->fuse_direct_read-->__fuse_direct_read-->fuse_direct_io
--->fuse_get_req--->__fuse_get_req
static struct fuse_req *__fuse_get_req(struct fuse_conn *fc, unsigned npages,
bool for_background)
{
...
err = -ENOTCONN;
if (!fc->connected)
goto out;
...
}
下面要定位的就是,为什么出问题的java线程会没有判断read的返回值,而不停重试呢?
gdb跟踪一下:
(gdb) bt
#0 0x00007fec3a56910d in read () at ../sysdeps/unix/syscall-template.S:81
#1 0x00007fec3a4f5c04 in _IO_new_file_underflow (fp=0x7fec0c01ae00) at fileops.c:593
#2 0x00007fec3a4f6dd2 in __GI__IO_default_uflow (fp=0x7fec0c01ae00) at genops.c:414
#3 0x00007fec3a4f163e in _IO_getc (fp=0x7fec0c01ae00) at getc.c:39
#4 0x00007febeacc6738 in get_totalticks.constprop.4 () from /usr/local/paas-agent/HeyTap-Linux-x86_64-11.0.7/lib/libmanagement_ext.so
#5 0x00007febeacc6e47 in Java_com_sun_management_internal_OperatingSystemImpl_getSystemCpuLoad0 ()
from /usr/local/paas-agent/HeyTap-Linux-x86_64-11.0.7/lib/libmanagement_ext.so
确认了用户态在循环进入 _IO_getc的流程,然后jvm的甘兄开始走查代码,发现有一个代码非常可疑:
UnixOperatingSystem.c:get_totalticks 函数,会有一个循环如下:
static void next_line(FILE *f) {
while (fgetc(f) != '\n');
}
从fuse模块的代码看,fc->connected = 0的地方并不是很多,基本都是abort或者fuse_put_super调用导致,通过对照java线程升高的时间点以及查看journal的日志,进一步fuse的挂载点失效有用户态文件系统lxcfs退出,而挂载点还有inode在访问导致,导致又无法及时地回收这个super_block,新的挂载使用的是:
remount_container() {
export f=/proc/stat && test -f /var/lib/baymax/lxcfs/$f && (umount $f; mount --bind /var/lib/baymax/lxcfs/$f $f)
使用了bind模式,具体bind的mount的特性可以参照网上的资料。
并且在journal日志中,查看到了关键的:
: umount: /proc/stat: target is busy
这行日志表明,当时卸载时出现失败,不是所有的访问全部关闭。
三、故障复现
1.针对以上的怀疑点,写了一个很简单的demo如下:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#define SCNd64 "I64d"
#define DEC_64 "%"SCNd64
static void next_line(FILE *f) {
while (fgetc(f) != '\n');//出错的关键点
}
#define SET_VALUE_GDB 10
int main(int argc,char* argv[])
{
unsigned long i =1;
unsigned long j=0;
FILE * f=NULL;
FILE *fh;
unsigned long userTicks, niceTicks, systemTicks, idleTicks;
unsigned long iowTicks = 0, irqTicks = 0, sirqTicks= 0;
int n;
if((fh = fopen("/proc/stat", "r")) == NULL) {
return -1;
}
n = fscanf(fh, "cpu " DEC_64 " " DEC_64 " " DEC_64 " " DEC_64 " " DEC_64 " "
DEC_64 " " DEC_64,
&userTicks, &niceTicks, &systemTicks, &idleTicks,
&iowTicks, &irqTicks, &sirqTicks);
// Move to next line
while(i!=SET_VALUE_GDB)----------如果挂载点不变,则走这个大循环,单核cpu接近40%
{
next_line(fh);//挂载点一旦变化,返回 ENOTCONN,则走这个小循环,单核cpu会接近100%
j++;
}
fclose(fh);
return 0;
}
执行以上代码,获得结果如下:
#gcc -g -o caq.o caq.c
一开始单独运行./caq.o,会看到cpu占用如下:
628957 root 20 0 8468 612 484 R 32.5 0.0 18:40.92 caq.o
发现cpu占用率时32.5左右,
此时挂载点信息如下:
crash> mount -n 628957 |grep lxcfs
ffff88a5a2619800 ffff88a1ab25f800 fuse lxcfs /rootfs/proc/stat
ffff88cf53417000 ffff88a4dd622800 fuse lxcfs /rootfs/var/lib/baymax/var/lib/baymax/lxcfs
ffff88a272f8c600 ffff88a4dd622800 fuse lxcfs /rootfs/proc/cpuinfo
ffff88a257b28900 ffff88a4dd622800 fuse lxcfs /rootfs/proc/meminfo
ffff88a5aff40300 ffff88a4dd622800 fuse lxcfs /rootfs/proc/uptime
ffff88a3db2bd680 ffff88a4dd622800 fuse lxcfs /rootfs/proc/stat/proc/stat
ffff88a2836ba400 ffff88a4dd622800 fuse lxcfs /rootfs/proc/diskstats
ffff88bcb361b600 ffff88a4dd622800 fuse lxcfs /rootfs/proc/swaps
ffff88776e623480 ffff88a4dd622800 fuse lxcfs /rootfs/sys/devices/system/cpu/online
由于没有关闭/proc/stat的fd,也就是进行大循环,然后这个时候重启lxcfs挂载:
#systemctl restart lxcfs
重启之后,发现挂载点信息如下:
crash> mount -n 628957 |grep lxcfs
ffff88a5a2619800 ffff88a1ab25f800 fuse lxcfs /rootfs/proc/stat
ffff88a3db2bd680 ffff88a4dd622800 fuse lxcfs /rootfs/proc/stat/proc/stat------------这个挂载点,由于fd未关闭,所以卸载肯定失败,可以看到super_block是重启前的
ffff887795a8f600 ffff88a53b6c6800 fuse lxcfs /rootfs/var/lib/baymax/var/lib/baymax/lxcfs
ffff88a25472ae80 ffff88a53b6c6800 fuse lxcfs /rootfs/proc/cpuinfo
ffff88cf75ff1e00 ffff88a53b6c6800 fuse lxcfs /rootfs/proc/meminfo
ffff88a257b2ad00 ffff88a53b6c6800 fuse lxcfs /rootfs/proc/uptime
ffff88cf798f0d80 ffff88a53b6c6800 fuse lxcfs /rootfs/proc/stat/proc/stat/proc/stat--------bind模式挂载,会新生成一个/proc/stat
ffff88cf36ff2880 ffff88a53b6c6800 fuse lxcfs /rootfs/proc/diskstats
ffff88cf798f1f80 ffff88a53b6c6800 fuse lxcfs /rootfs/proc/swaps
ffff88a53f295980 ffff88a53b6c6800 fuse lxcfs /rootfs/sys/devices/system/cpu/online
cpu立刻打到接近100%
628957 root 20 0 8468 612 484 R 98.8 0.0 18:40.92 caq.o
四、故障规避或解决
我们的解决方案是:
1.在openjdk中的 fgetc 中增加返回值的判断,不能无脑死循环。
2.由于lxcfs在3.1.2 的版本会有段错误导致退出的bug,尽量升级到4.0.0以上版本,不过这个涉及到libfuse动态库的版本适配。
3.在lxcfs等使用fuse的用户态文件系统进行remount的时候,如果遇到umount失败,则延迟加重试,超过次数通过日志告警系统抛出告警。
java单线程100%利用率的更多相关文章
- JAVA单线程以及java多线程的实现方式
1.java单线程的实现 public class SingletonThread { @SuppressWarnings("static-access") public stat ...
- jstack命令定位java程序CPU利用率高的代码位置
高手是怎么使用jstack精确找到异常代码的(java程序CPU利用率高的情况) 请jstack神器来帮忙 本文介绍Linux环境下使用jstack定位问题的秘笈1.[top命令]找到CPU利用率持续 ...
- Java死锁排查和Java CPU 100% 排查的步骤整理
================================================= 人工智能教程.零基础!通俗易懂!风趣幽默!大家可以看看是否对自己有帮助! 点击查看高清无码教程 == ...
- JAVA单线程和多线程的实现方式
1.java单线程的实现 一个任务一个人独立完成 public class SingletonThread { @SuppressWarnings("static-acce ...
- [Java] CPU 100% 原因查找解决
CPU 100%肯定是出现死锁,这个时候观察内存还是够用的,但是CPU一直100%,以下几步解决: 1. 找到进程消耗cpu最大的 $top top - :: up days, :, user, lo ...
- 使用Java对100以内偶数求和
/** * 根据for循环的描述: for(变量初始化:循环条件:修改循环变量的值),求出100以内的所有偶数,for(int i = 0; i<=100; i+=2),把这些偶数累加到一个空的 ...
- 记一次java应用cpu利用率过高调试经历
1,现象 写的一个storm应用,主要是通过mysql的binlog来同步表到hbase.运行一段时间后发现,经常会出现cpu使用率飙升到200%以上,然后各种消息堆积报警等等出现各种问题 2,调研过 ...
- CCF201409-2 画图 java(100分)
试题编号: 201409-2 试题名称: 画图 时间限制: 1.0s 内存限制: 256.0MB 问题描述: 问题描述 在一个定义了直角坐标系的纸上,画一个(x1,y1)到(x2,y2)的矩形指将横坐 ...
- CCF201509-2 日期计算 java(100分)
试题编号: 201509-2 试题名称: 日期计算 时间限制: 1.0s 内存限制: 256.0MB 问题描述: 问题描述 给定一个年份y和一个整数d,问这一年的第d天是几月几日? 注意闰年的2月有2 ...
随机推荐
- JDK1.7HashMap源码分析
1.1首先HashMap中的Hash(哈希)是什么? Hash也称散列,哈希,对应的英文都是Hash.基本原理就是把任意长度的输入通过Hash算法变成固定长度的输出,这个映射的规则就是对应的Ha ...
- 字节开源RPC框架Kitex的日志库klog源码解读
前言 这篇文章将着重于分析字节跳动开源的RPC框架Kitex的日志库klog的源码,通过对比Go原生日志库log的实现,探究其作出的改进. 为了平滑学习曲线,我写下了这篇分析Go原生log库的文章,希 ...
- es6.4.2api
这是讲数据库的数据导入到es里 所有用到了mysql! 1.依赖 <?xml version="1.0" encoding="UTF-8"?> & ...
- 开源流程引擎camunda如何扩展
市场上基于Java语言的开源工作流引擎有:osworkflow.jbpm.activiti.flowable.camunda等,其中osworkflow.jbpm流程引擎已经过时,目前主流的开源 ...
- .NET 处理[未能为 SSLTLS 安全通道建立信任关系]问题
更新记录 2022年4月16日本文迁移自Panda666原博客,原发布时间:2021年7月16日. 在.NET的开发过程中,发现[基础连接已经关闭: 未能为 SSL/TLS 安全通道建立信任关系]问题 ...
- 【C#/VB.NET】 将PDF转为SVG/Image, SVG/Image转PDF
SVG是一种图形文件格式,它的英文全称为Scalable Vector Graphics,意思为可缩放的矢量图形.它在放大或者改变尺寸的情况下其图形质量不会有所损失,且与 JPG 和 GIF 图像比起 ...
- VMware 安装 Anolis OS
安装时参考的以下文章 VMware Workstation Pro 虚拟机安装 VMware Workstation Pro 安装 龙蜥操作系统(Anolis OS) Anolis OS 8 安装指南 ...
- JS:object
object:对象 1.对象是拥有属性和方法的数据,也是一个变量,但值有多个,以key-value的形式. 2.对象有继承属性: 每当创建一个对象,对象里面就会有一个原型对象prototype,可以从 ...
- 面向对象的封装(粘贴Markdown代码解决缩进问题)
直接粘贴idea的代码会导致缩进错乱,建议先粘贴到记事本再粘贴到笔记!!! 1.先将属性私有化,再对外提供简单的接口可以访问内部.如set.get方法 2.set方法:修改年龄 public void ...
- python报错合集
哈喽,大家好呀 我又来啦,今天让我们来看看python中有哪些常见的异常报错吧 说到python中的报错,我们总是脑壳疼现在我们要学会去认识报错的类型 这样子,在我们出现报错的时候就可以知道报错的原因 ...