寫這篇文章源自查看一個dump,CEF在創建位圖數據的時候崩潰了。
我很納悶爲什麼會失敗,我首先想到的是程序是不是有資源泄露,導致GDI句柄數過高導致。
把堆棧打印出來發現GDI資源個數0x564個,User對象個數0x5e5個,都才一千多個,應該不多吧。截圖如下:
所以又查詢了下window對GDI和USER對象的個數限制。
msdn介紹GDI對象: https://docs.microsoft.com/zh-cn/windows/win32/sysinfo/gdi-objects
對個數的限制:
There is a theoretical limit of 65,536 GDI handles per session. However, the maximum number of GDI handles that can be opened per session is usually lower, since it is affected by available memory.
Windows 2000: There is a limit of 16,384 GDI handles per session.
There is also a default per-process limit of GDI handles. To change this limit, set the following registry value:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows\GDIProcessHandleQuota
This value can be set to a number between 256 and 65,536.
Windows 2000: This value can be set to a number between 256 and 16,384.
系統對每個session的限制是65536(64KB),對每個進程的限制可以通過註冊表來修改,範圍是256-65536
windows 2000的比較小,做了特殊說明。
特別主要的是專門提到了,會受可用內存的影響。
However, the maximum number of GDI handles that can be opened per session is usually lower, since it is affected by available memory.
msdn介紹USER對象: https://docs.microsoft.com/zh-cn/windows/win32/sysinfo/user-objects
對個數的限制:
There is a theoretical limit of 65,536 user handles per session. However, the maximum number of user handles that can be opened per session is usually lower, since it is affected by available memory. There is also a default per-process limit of user handles. To change this limit, set the following registry value:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows\USERProcessHandleQuota
This value can be set to a number between 200 and 18,000.
系統對每個session的限制是65536(64KB),對每個進程的限制可以通過註冊表來修改,範圍是200 -18000
同樣也說明了會受到可用內存的影響!
那麼dump中查看到當時進程使用的GDI和USER對象個數都是1000多,會不會超過了限制了,需要看下這兩個註冊表值:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows\GDIProcessHandleQuota
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows\USERProcessHandleQuota
在我win7和win10電腦上默認值都是10000,所以是遠遠沒有超過限制的。
最後,那就很可能是內存不足的原因了吧,然後看了下內存使用情況,果然佔用比非常高,可用物理內存也比較小。最終導致創建GDI資源失敗了。
相關API:
GetGuiResources 獲取GDI或者USER對象的個數
https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getguiresources
DWORD dwCount = 0;
dwCount = ::GetGuiResources(GetCurrentProcess(), GR_GDIOBJECTS); // 獲取當前進程GDI對象個數
dwCount = ::GetGuiResources(GR_GLOBAL, GR_GDIOBJECTS); // 獲取所有進程對象個數
dwCount = ::GetGuiResources(GetCurrentProcess(), GR_USEROBJECTS); // 獲取當前進程USER對象個數
dwCount = ::GetGuiResources(GR_GLOBAL, GR_USEROBJECTS); // 獲取所有進程USER對象個數
函數第二個參數可以使用另外兩個參數:GR_GDIOBJECTS_PEAK,GR_USEROBJECTS_PEAK
不過是需要windows 7 或者 Windows Server 2008 R2以及以上版本才行。
Return the peak count of GDI objects.
Windows Server 2008, Windows Vista, Windows Server 2003 and Windows XP: This value is not supported until Windows 7 and Windows Server 2008 R2.
參考資料:
https://www.xuebuyuan.com/1029053.html
無關的話題:
msdn中GDI對象以及USER對象放在同一個目錄講解的還包括內核對象:
https://docs.microsoft.com/zh-cn/windows/win32/sysinfo/kernel-objects
文章中的幾個圖對於理解內核對象的生命週期以及跨進程使用很有幫助。