人间四月天,bug无处钻

2023-11-23 02:40
文章标签 bug 人间 无处 四月

本文主要是介绍人间四月天,bug无处钻,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

你是人间的四月天

笑响点亮了四面风

轻灵在春的光艳中交舞着变

图片

一. 缘来缘起

人间四月天,bug无处钻,让bug没有藏身之地。今天,我们来聊句柄泄漏的定位。部分朋友遇到性能问题时,束手无策。别担心,我们一起实践,不信你搞不定。

另外,性能优化,也是区分初级工程师和高级工程师的标志之一。在面试的时候,经常会被问到如何做性能优化,那些只背诵过八股文考试题目的人,有可能歇菜。

遇到性能瓶颈,该如何去优化呢?本文用实战例子,带大家一起来查问题,干货满满,建议有兴趣的朋友,亲自试一下。不仅为笔试面试,更重要是为实际工作。

二. 句柄泄漏

曾经,百度笔试的一个题目为:

一个进程能打开多少文件句柄?

看到这个问题,有的人懵圈了,还说不知道啊。其实,这个问题并不是考查你的记忆能力,而是考查你有没有一定的实战经验。

我们直接用ulimit -a命令来看下:

  • ubuntu@VM-0-15-ubuntu:~$ ulimit -acore file size (blocks, -c) 0data seg size (kbytes, -d) unlimitedscheduling priority (-e) 0file size (blocks, -f) unlimitedpending signals (-i) 3301max locked memory (kbytes, -l) 64max memory size (kbytes, -m) unlimitedopen files (-n) 1024pipe size (512 bytes, -p) 8POSIX message queues (bytes, -q) 819200real-time priority (-r) 0stack size (kbytes, -s) 8192cpu time (seconds, -t) unlimitedmax user processes (-u) 3301virtual memory (kbytes, -v) unlimitedfile locks (-x) unlimitedubuntu@VM-0-15-ubuntu:~$

可以看到,在我的机器上,一个进程能打开的最大句柄数是1024,我们来写个简单程序测试一下:

#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <fcntl.h>
int fun() {        static int count = 0;        count++;
        int fd = open("/dev/urandom", O_RDONLY);        if(-1 == fd)        {                printf("error, %d\n", count);                exit(1);        }
        // close(fd);   // do not close fd, just for testing
        return 0;}
int main(){        while(1)        {                fun();        }}

编译运行,结果为:

  • ubuntu@VM-0-15-ubuntu:~$ gcc a.c && ./a.outerror, 1022ubuntu@VM-0-15-ubuntu:~$

想一下,这是为什么?很显然,第1022次调用出错了,也就是说,已经成功生成了1021个句柄。不是能生成1024个句柄吗?另外3个去哪里呢?显然,另外3个是:标准输入、标准输出、标准错误。

可见,进程打开的句柄最大数,确实是1024(不同环境可能不一样)。那么,如果句柄一直不关掉,持续上涨,就会造成资源泄漏,导致系统性能降低,而且会导致程序出错,这是慢性病,严重得很。

三. 定位方法

下面,我们看一段有句柄泄漏的程序:

#include <stdio.h>#include <unistd.h>#include <fcntl.h>
int getRand() {        int randNum = 0;        int fd = open("/dev/urandom", O_RDONLY);        if(-1 == fd)        {                // handle error        }
        read(fd, (char *)&randNum, sizeof(int));        return randNum;}
int main(){        while(1)        {                getRand();                sleep(1);        }}

有的朋友要说,这简单啊,一眼就知句柄泄漏。其实不然,实际工程的代码,经常是几万几十万行,肉眼看是不行的。

而且,很多时候,压根就不知道哪个进程在泄漏句柄。所以,我们首先要搞清楚的是,到底是哪个进程正在泄漏句柄。

现在,我们编译上述程序,生成a.out,然后运行,还说什么呢?各种linux命令,来搞个组合拳啊,查出泄漏的进程。

运行a.out后,让它泄漏一会儿,然后看看:

ubuntu@VM-0-15-ubuntu:~$ lsof -n|awk '{print $2}'| sort | uniq -c | sort -nr | head    116 1175    104 13433     52 9786     48 13454     44 994     40 1331     40 1130     35 8200     32 1385     32 13485ubuntu@VM-0-15-ubuntu:~$ lsof -n|awk '{print $2}'| sort | uniq -c | sort -nr | head    116 1175    104 13433     54 9786     48 13454     44 994     40 1331     40 1130     35 8200     32 1385     32 13485ubuntu@VM-0-15-ubuntu:~$ lsof -n|awk '{print $2}'| sort | uniq -c | sort -nr | head    116 1175    104 13433     63 9786     48 13454     44 994     40 1331     40 1130     35 8200     32 1385     32 13485ubuntu@VM-0-15-ubuntu:~$

看到没,这个组合命令,就是统计进程打开句柄数的,然后,你看,有个进程打开句柄的数量一直在上升,显然就是进程9786了,我们进一步看看究竟是何方妖怪:​​​​​​​

ubuntu@VM-0-15-ubuntu:~$ ps -aux | grep 9786ubuntu    9786  0.0  0.0   4216   776 pts/0    S+   20:07   0:00 ./a.outubuntu   10402  0.0  0.1  13228  1084 pts/1    S+   20:10   0:00 grep 9786ubuntu@VM-0-15-ubuntu:~$

快看,找出了是a.out进程,感觉快要成功了。那么,我们看看这个进程在打开哪些句柄,如下:

ubuntu@VM-0-15-ubuntu:~$ ll /proc/9786/fdtotal 0lrwx------ 1 ubuntu ubuntu 64 Mar 30 20:07 0 -> /dev/pts/0lrwx------ 1 ubuntu ubuntu 64 Mar 30 20:07 1 -> /dev/pts/0lr-x------ 1 ubuntu ubuntu 64 Mar 30 20:07 10 -> /dev/urandomlr-x------ 1 ubuntu ubuntu 64 Mar 30 20:12 100 -> /dev/urandomlr-x------ 1 ubuntu ubuntu 64 Mar 30 20:12 101 -> /dev/urandomlr-x------ 1 ubuntu ubuntu 64 Mar 30 20:12 102 -> /dev/urandomlr-x------ 1 ubuntu ubuntu 64 Mar 30 20:12 103 -> /dev/urandomlr-x------ 1 ubuntu ubuntu 64 Mar 30 20:12 104 -> /dev/urandom

从这里,就基本知道进程在打开/dev/urandom了。于是,直接在a.out进程对应的代码中搜索一下,就知道代码的位置了,然后就知道句柄泄漏的地方了。

可是,有的朋友还不放心,想知道a.out进程到底在干啥,那也可以,strace命令搞起,直接来看进程在做什么,如下:​​​​​​​

ubuntu@VM-0-15-ubuntu:~$ sudo strace -p 9786strace: Process 9786 attachedrestart_syscall(<... resuming interrupted nanosleep ...>) = 0open("/dev/urandom", O_RDONLY)          = 331read(331, "\2203\263\244", 4)           = 4nanosleep({1, 0}, 0x7fff31962d30)       = 0open("/dev/urandom", O_RDONLY)          = 332read(332, "\20S\367 ", 4)               = 4nanosleep({1, 0}, 0x7fff31962d30)       = 0open("/dev/urandom", O_RDONLY)          = 333read(333, "bE\351\267", 4)              = 4nanosleep({1, 0}, 0x7fff31962d30)       = 0open("/dev/urandom", O_RDONLY)          = 334read(334, "\265\16\2273", 4)            = 4

这下就完全清楚了,原来,进程在执行:

open("/dev/urandom", O_RDONLY)

这回更清楚了,所以,还说什么呢?直接去代码中搜索吧,然后找到句柄泄漏的地方,然后就发现,原程序中,没有close(fd)的操作。So nice.

四. 修复验证

从上述分析可知,需要关掉句柄fd, 故修复后的代码为:

  • #include <stdio.h>#include <unistd.h>#include <fcntl.h> int getRand() { int randNum = 0; int fd = open("/dev/urandom", O_RDONLY); if(-1 == fd) { // handle error } read(fd, (char *)&randNum, sizeof(int)); close(fd); return randNum;} int main(){ while(1) { getRand(); sleep(1); }}

经用同样的方法验证,再无句柄泄漏,问题搞定。

五. 最后的话

思路和工具,都很重要。在本文中,我们可以回顾一下,是怎样一步一步打开局面的?又是怎么灵活运用各种linux命令工具的?

对于笔试面试而言,八股文刷题要搞,也要注重实战经验积累,不然一问就歇菜。在实际工作中,各种思路和工具,亦不可或缺。

希望有兴趣的朋友实际操作一下,感受一下bug的定位过程,以后笔试、面试和工作中,就有谈资啦。人间四月天,我们下次见。

这篇关于人间四月天,bug无处钻的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/414753

相关文章

JavaBug系列-解决SpringBoot返回Xml结构的问题

JavaBug系列之SpringBoot返回Xml结构的问题 Java医生一、关于错误信息二、如何解决问题 Java医生 本系列记录常见Bug,以及诊断过程和原因 作者:Java医生 教学: Java企业项目辅导,专注于辅导新入职员工,解决各种问题! V:study_51ctofx 一、关于错误信息 如图,SpringBoot请求返回Xml格式信息 通过以上信息分析,

JavaBug系列- Failed to load driver class com.mysql.cj.jdbc.Driver in either of HikariConfig class load

JavaBug系列之Mysql驱动问题 Java医生一、关于错误信息二、如何解决问题 Java医生 本系列记录常见Bug,以及诊断过程和原因 Java/一对一零基础辅导/企业项目一对一辅导/日常Bug解决/代码讲解/毕业设计等 V:study_51ctofx 一、关于错误信息 APPLICATION FAILED TO START Description: Fai

【解决bug之路】npm install node-sass(^4.14.1)连环报错解决!!!(Windows)

有关node-sass的深入分析可参考:又报gyp ERR!为什么有那么多人被node-sass 坑过? 主要有如下三方面错误,请自查: 1.node,npm版本需与node-sass版本匹配,像node-sass(^4.14.1)就得node 14.x版本才可以,node 16不行 gyp ERR! build error15 gyp ERR! stack Error: `

排查 MyBatis XML 配置中的 IF 语句与传值名称不匹配的 Bug

文章目录 本文档只是为了留档方便以后工作运维,或者给同事分享文档内容比较简陋命令也不是特别全,不适合小白观看,如有不懂可以私信,上班期间都是在得 前言,在改一个bug得时候发现一个有意思得问题,就是mybatis得xml中if判断得问题,传值名字不匹配依旧可以进行判断,如下图 传值userName,但是有意思得事情出现了,进了if,并且没有报错,尝试了两次都是这

彻底解决魅族手机无法彻底卸载应用的bug

使用Flyme系统的同学可能会遇到一个问题: 卸载了某些软件(例如通过开发者模式调试安装的应用)后,实际这个应用还残留在系统,当你用低版本或者其他签名的apk覆盖安装的时候会提示“安装失败”,要求你卸载后重新安装。 可是无论你从应用列表寻找还是清理垃圾,都根本找不到这个应用。 闹鬼?这个bug一直伴随着flyme,可怜工程师们竟然一个都没发现。 今天笔者教大家一招解决这个问题。

今天做了freemaker 导出word文档 的bug修复,解决 \n换行 问题

结合Freemaker导出文件 public void exportSimpleWord() throws Exception{// 要填充的数据, 注意map的key要和word中${xxx}的xxx一致Map<String,String> dataMap = new HashMap<String,String>();dataMap.put("username", "张三");dataMap.

【软件测试】软件测试-----什么是Bug?Bug是如何分级的?Bug的生命周期是怎样的?如何描述一个Bug?

博客目录 一.软件测试的生命周期二.BUG的定义和级别2.1 bug的概念.2.2 如何描述一个bug.2.3bug的级别2.3.1 bug分级的意义.2.3.2 bug的四种级别. 三.BUG的生命周期.四.当与开发人员发生冲突该如何处理(高频面试)五.总结 一.软件测试的生命周期 软件测试贯穿于软件的整个生命周期,针对这句话我们一起来看一下软件测试是如何贯穿软件的整个生命周

【ListView】有关填充bug

假设要实现如下效果 ListView里若使用RelativeLayot则会出现错误 得不到垂直线的填充 线性布局的高度设置为match_parent会使高度为0,必须给它一个固定值,比如20dp ListView建议使用LinearLayout在最外层,而最外层的宽高无论如何设置都无法确定的,建议第二层也使用linearLayout来布局 【实现

ListView数据只有显示不全bug

Android编程中,ScrollView嵌套ListView时,会无法正确的计算ListView的大小。解决的办法有如下两种: 解决方案1: 直接把包含ListView控件的ScrollView控件从布局文件中去除,留下ListView控件,这是最简单快捷的解决办法,如果一定要在ScrollView中包含ListView,则参考解决方案2:

ScrollView嵌套listview滑动位置底部bug

三种方案,个人推荐第三种: 1 sv.fullScroll(ScrollView.FOCUS_UP); 2 sv.setScrollY(0); 3 sv.smoothScrollTo(0,20); 滑动冲突解决listView: public class ListViewForScrollView extends ListView {public ListVie