目录
1、查看任务管理器,发现程序中有明显的GDI对象泄漏
2、使用GDIView工具查看发生泄漏的是哪一种GDI对象
3、尝试找到复现问题的方法,缩小排查范围,逐步地找到GDI对象的泄漏点
4、本案例中的相关细节点的思考与总结(有价值的细节点)
4.1、UI界面无法显示的原因分析
4.2、使用GDIView工具查看泄漏的对象,并尝试找到问题复现的办法,缩小代码排查的范围
4.3、如果还是无法定位问题,可以尝试使用历史版本比对法等其他方法来辅助排查
5、最后
C++软件异常排查从入门到精通系列教程(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/125529931C/C++实战进阶(已更新到400多篇,持续更新中...)https://blog.csdn.net/chenlycly/category_11931267.htmlVC++常用功能开发汇总(专栏文章列表,欢迎订阅,持续更新...)https://blog.csdn.net/chenlycly/article/details/124272585C++ 软件开发从入门到精通(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_12695902.htmlC++软件分析工具从入门到精通案例集锦(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/article/details/131405795开源组件及数据库技术(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_12458859.html网络编程与网络问题分享(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_2276111.html 最近项目中又遇到了一个GDI对象泄漏的问题,这个问题虽然不难排查,但其中涉及的部分细节点很有价值,所以今天依托这个案例再写一篇文章,详细讲述这个问题的完整排查过程,并对案例中涉及的若干细节点进行详细的总结,以供大家借鉴或参考。
1、查看任务管理器,发现程序中有明显的GDI对象泄漏
最近测试同事在拷机测试一段时间后,软件出现了问题,很多界面显示不出来了,双击托盘图标后主界面也显示不出来了。
打开任务管理器,查看进程的资源占用,内存占用是正常的,看到进程占用的GDI对象总数是有问题的,居然达到9000多个:
应该是程序中发生GDI对象泄漏了。进程的GDI上限是1万个,当GDI对象总数达到或接近1万时,用于绘图的GDI函数就会出异常,绘图失败,窗口绘制失败就会显示异常。所以当前窗口显示不出来,所以当前的这个窗口显示不出来的问题,肯定是GDI对象泄漏导致的。光知道程序中有GDI对象泄漏是不够的,还要使用GDIView工具查看具体是哪一类GDI对象有泄漏,这样才有明确的排查目标。
关于Windows程序进程的GDI对象的上限值的详细说明,可以查看我的文章:
从注册表看Windows系统进程GD对象及进程句柄数上限https://blog.csdn.net/chenlycly/article/details/139565038 此外,注意一下,Windows任务管理器的进程列表中默认是不显示GDI对象的,可以右键点击进程列表的列表头,在弹出的右键菜单中点击“选择列”:
然后在弹出的窗口中找到GDI对象选项:
勾选上即可看到进程的GDI对象总数。
正常情况下,进程的GDI对象总数最多只会有上千个左右,如果有好几千,一般就能确定程序中发生GDI对象泄漏了。
2、使用GDIView工具查看发生泄漏的是哪一种GDI对象
于是将查看GDI对象占用的工具GDIView发给测试同事,打开后看到DC对象占的特别高,竟然高达7000多个:
应该是DC对象发生泄漏了。DC对象发生泄漏可能存在两个场景:
1)调用GetDC后,没有调用ReleaseDC去释放;
2)调用CreateCompatibleDC后,没有调用DeleteDC去释放。
一般GDI对象泄漏都发生在处理窗口绘制的UI模块中,但也少部分模块会涉及,比如处理桌面共享图像采集的音视频模块,音视频模块的桌面图像采集、视频图像绘制可能会使用到GDI函数进行绘图,会使用到GDI对象。
使用GDIView工具只能确定是DC对象发生了泄漏,但看不到其他信息,要找出DC对象的泄漏点,只能去查看源码。但程序的模块太多,还需要尝试复现问题,找到问题的复现规律,根据复现问题的相关操作,缩小排查的范围,逐步地锁定泄漏点。
在这里,给大家重点推荐一下我的几个热门畅销专栏,欢迎订阅:(博客主页还有其他专栏,可以去查看)
专栏1:(该精品技术专栏的订阅量已达到510多个,专栏中包含大量项目实战分析案例,有很强的实战参考价值,广受好评!专栏文章持续更新中,预计更新到200篇以上!欢迎订阅!)
C++软件调试与异常排查从入门到精通系列文章汇总https://blog.csdn.net/chenlycly/article/details/125529931
本专栏根据多年C++软件异常排查的项目实践,系统地总结了引发C++软件异常的常见原因以及排查C++软件异常的常用思路与方法,详细讲述了C++软件的调试方法与手段,以图文并茂的方式给出具体的项目问题实战分析实例(很有实战参考价值),带领大家逐步掌握C++软件调试与异常排查的相关技术,适合基础进阶和想做技术提升的相关C++开发人员!
考察一个开发人员的水平,一是看其编码及设计能力,二是要看其软件调试能力!所以软件调试能力(排查软件异常的能力)很重要,必须重视起来!能解决一般人解决不了的问题,既能提升个人能力及价值,也能体现对团队及公司的贡献!
专栏中的文章都是通过项目实战总结出来的,包含大量项目问题实战分析案例,有很强的实战参考价值!专栏文章还在持续更新中,预计文章篇数能更新到200篇以上!
专栏2:
C++常用软件分析工具从入门到精通案例集锦汇总(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/article/details/131405795
常用的C++软件辅助分析工具有SPY++、PE工具、Dependency Walker、GDIView、Process Explorer、Process Monitor、API Monitor、Clumsy、Windbg、IDA Pro等,本专栏详细介绍如何使用这些工具去巧妙地分析和解决日常工作中遇到的问题,很有实战参考价值!
专栏3:(本专栏涵盖了C++多方面的内容,是当前重点打造的专栏,专栏文章已经更新到400多篇,持续更新中...)
C/C++实战进阶(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_11931267.html
以多年的开发实战为基础,总结并讲解一些的C/C++基础与项目实战进阶内容,以图文并茂的方式对相关知识点进行详细地展开与阐述!专栏涉及了C/C++领域多个方面的内容,包括C++基础及编程要点(模版泛型编程、STL容器及算法函数的使用等)、数据结构与算法、C++11及以上新特性(不仅看开源代码会用到,日常编码中也会用到部分新特性,面试时也会涉及到)、常用C++开源库的介绍与使用、代码分享(调用系统API、使用开源库)、常用编程技术(动态库、多线程、多进程、数据库及网络编程等)、软件UI编程(Win32/duilib/QT/MFC)、C++软件调试技术(排查软件异常的手段与方法、分析C++软件异常的基础知识、常用软件分析工具使用、实战问题分析案例等)、设计模式、网络基础知识与网络问题分析进阶内容等。
专栏4:
VC++常用功能开发汇总(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/article/details/124272585
将10多年C++开发实践中常用的功能,以高质量的代码展现出来。这些常用的高质量规范代码,可以直接拿到项目中使用,能有效地解决软件开发过程中遇到的问题。
专栏5:
C++ 软件开发从入门到精通(专栏文章,持续更新中...)https://blog.csdn.net/chenlycly/category_12695902.html
根据多年C++软件开发实践,详细地总结了C/C++软件开发相关技术实现细节,分享了大量的实战案例,很有实战参考价值。
3、尝试找到复现问题的方法,缩小排查范围,逐步地找到GDI对象的泄漏点
于是让测试同事能否找到复现的规律,在复现的过程中查看GDIView中DC对象的变化,看看是什么操作引发的GDI对象泄漏。测试同事也确实有一套,很快找到了复现规律,将视频窗口全屏后发起桌面共享,然后观察GDIView中当前进程的DC对象数目在快速的上升。
首先是从UI模块查起,搜索调用GetDC和CreateCompatibleDC的代码,看下来发现没有明显的问题。
本来想在我的开发机器上复现,如果复现了,也方便我们开发人员自行排查,但一直复现不了。当前出问题的机器是微软Surface平板电脑,这个平板电脑屏幕分辨率比较大,但屏幕的物理尺寸比较小,如果系统按100%先是,桌面图标和软件界面会显示的特别小,根本没法使用,必须调整系统的显示比例才能正常使用,比如可以调整到200%:
所以怀疑当前这个问题,可能和系统设置的比例有关系,于是手动将我们用的PC系统的显示比例调整到非100%。但调整后还是没有复现问题,所以可能和显示比例没有直接的关系,可能与设备有关,只能在微软的Surface平板上才能复现问题。
既然UI模块没有找到明显的问题,那结合发送桌面共享时才有明显泄漏的现象,可能是负责桌面图像采集的音视频模块的代码中有泄漏。于是找到维护该模块的音视频开发组的同事,让他重点看看发送共享时桌面图像采集的代码,看看有没有使用到DC对象,是否存在DC对象没有释放的问题。
后来考虑到音视频开发组的同事对UI编程细节不了解,直接去和他们一起去查看代码,以快速排查并确定问题。于是和同事一起去查采集桌面图像的代码,果然找到了问题,先是调用GetDC接口获取桌面窗口的DC:
但在使用完该DC对象之后,在当前函数的结尾处,并没有调用ReleaseDC去释放,所以产生了DC对象泄漏。
在上述问题代码中,在函数结尾处居然使用ReleaseDC去释放一个调用CreateCompatibleDC接口创建的DC(m_hShowMemDC):
而不去释放GetDC获取的DC(hDesktopDC)。应该还是音频开发组的同事对GDI UI界面编程不熟悉导致的,调用CreateCompatibleDC创建的DC对象,使用完成后,应该使用DeleteDC去释放;使用GetDC获取的DC,使用完成后,应该使用ReleaseDC去释放。
有些人可能会有误解,可能会觉得,使用CreateCompatibleDC创建的DC才需要去释放,使用GetDC获取的DC是不需要释放的。
4、本案例中的相关细节点的思考与总结(有价值的细节点)
这个问题排查起来虽然不复杂,但其中涉及到的一些细节点,很有价值,在这里给大家详细总结一下。
4.1、UI界面无法显示的原因分析
如果UI界面显示不出来,可能是以下原因导致的:
1)资源占用过多
当内存或CPU资源占用过多,软件出现明显的卡顿。之前在微软Surface平板上遇到过,因为代码中在持续不断接收音视频数据,在不断的解码播放,占用了大量CPU资源,导致机器发热厉害,而Surface平板散热不好,CPU会降低频率,这样会占用更多的CPU,直接导致系统卡顿,软件也跟着异常卡顿。
2)UI线程堵塞卡死要看处理窗口事务的UI线程是否发生卡死堵塞,比如调用底层的接口一直没有返回,卡在WaitForSingleObject接口上,这是多线程死锁导致的。因为UI线程卡死了,点击窗口没法应了,本来要显示出来的窗口也显示不出来了。
3)GDI对象泄漏程序运行过程中有持续的GDI对象泄漏,当进程的GDI对象接近或达到1万个,负责窗口绘制的GDI函数会调用失败,窗口绘制不出来,导致窗口无法显示。本案例中的问题,就是这个原因导致的。
上述问题场景,我们在项目中都遇到过,而且不止一次遇到过!
4.2、使用GDIView工具查看泄漏的对象,并尝试找到问题复现的办法,缩小代码排查的范围
使用GDIView工具查看具体是哪个GDI对象发生了泄漏。确定泄漏对象之后,需要去查看代码去排查GDI对象的泄漏点。但软件的模块多,代码量大,不好漫无目标地排查。需要尝试去找到复现问题的办法,根据复现问题的操作步骤和场景,猜测问题可能出在哪些代码块中,即缩小代码排查的范围。本文中的案例,也正是这么做的,有效地缩小了排查范围,快速地找到GDI对象的泄漏点。
4.3、如果还是无法定位问题,可以尝试使用历史版本比对法等其他方法来辅助排查
如果上述方法很难查出问题,就可以尝试其他方法来辅助排查。如果当前的问题是最近才出现的或者最近几个月才出现的(如果时间过长,用历史版本比对法会比较费劲),可以使用历史版本比对法来辅助排查。
我之前就写过两篇使用历史版本比对法排查GDI对象泄漏的案例,有一定的参考价值:
使用GDIView工具排查GDI对象泄漏问题(常用分析工具)https://blog.csdn.net/chenlycly/article/details/125399896使用GDIView工具排查GDI对象泄漏导致C++程序UI界面绘制异常的问题https://blog.csdn.net/chenlycly/article/details/140731065 历史版本比对法,就是找出第一次出现问题的那个时间点,基本上问题就是前一天提交的代码或者底层新发布的库导致的,然后在svn或git上查看前一天的修改记录,就能快速地找到问题了。
但历史版本比对法,依赖一套完备的版本自动化编译系统,我以前多次提到过。比如我们的版本自动化编译系统,编译版本的机器会每天凌晨通过svn或git将代码和库都更新成最新的,然后发起编译,生成版本(生成安装包或者升级包),然后自动将版本拷贝到文件服务器上供大家使用。这样只要代码有修改或者有库发布,在文件服务器上都会生成对应的版本,如下所示:
这样我们就好执行历史版本比对法了,就是用二分法安装不同时间点的版本,看看从哪天开始出现内存泄漏的,然后查看前一天svn或git上的修改记录就能快速找到方向和线索了。
历史版本比对法,虽然执行起来比较笨拙,但确实很好用,也很实用,我们在项目中多次使用过,屡试不爽啊!
5、最后
案例本身可能比较简单,排查起来也很快,但案例中所涉及到的若干细节点可能很有价值,我们在问题复盘时要积极的思考与总结,也可以将之前遇到的一些问题及场景串联,进行归纳整理(多向自己提问,多问问自己为什么,要思维扩散,要联想串联)。有了这些思考和总结之后,下次在遇到类似问题时,就会有明确的排查思路和手段,能有效地提高问题排查的效率,能快速的定位问题。