本文共 12261 字,大约阅读时间需要 40 分钟。
翻译自:
什么是内存泄漏?
当一个进程分配了内存,它应该回收并释放给操作系统。如果由于代码里的缺陷没有回收内存,被称为泄漏,它会导致操作系统和应用程序的内存压力。有关SQL Server内存泄漏的神话 SQL Server内存管理设计为基于系统可用内存数量和SQL Server里Max server memory设置来动态增加和收缩内存。 很多次,系统管理员查看SQL Server内存使用,如果他们发现SQL Server内存使用很高会认为SQL Server内存泄漏了。 这是不对的,SQL Server是基于服务的应用程序,它的内存管理器设计为按需(除了大页)增加内存使用,除非Windows发出低内存提醒,否则不会收缩它的内存使用。我们可以在SQL Server里通过配置Max server memory来控制SQL Server内存使用。这个配置限制了SQL Server Bpool的使用,而不会控制整个SQL Server内存使用。分配在BPOOL以外(也叫MTL或MTR)的SQL Server内存部分,我们没有办法控制在bpool外SQL Server可以使用多少内存,但是non-bpool内存使用通常很低,可以很容易通过研究运行在SQL Server里的组件来预估。 例如:如果你想SQL Server在服务器上只使用10GB内存。考虑在Bpool以外SQL Server可能需要多少内存,并相应的设置“Max server memory”。在这里,如果你预估在Bpool以外SQL Server使用1.5GB,那设置Max server memory为8.5GB。什么可以导致SQL Server内存泄漏? SQL Server代码有一个逻辑去分配内存,但是不会回收它。如果在SQL Server里有任何组件导致了内存泄漏,它可以很容易使用像sys.dm_os_memory_allocation、sys.dm_os_memory_clerks和sys.dm_os_memory_objects等识别出来。但是,SQL Server里大多数的内存泄漏时由加载到SQL Server进程里的第三方DLL导致的。注意:加载到SQL Server里的非SQL Server DLL的所有内存分配将会产生“Mem to Leave”(在Bpool之外),并且他们被称为直接Windows分配(DWA)。 当在SQL Server里有内存溢出条件,如果你猜测有内存泄漏。首先确定谁在消耗内存。如果SQL Server没有使用在MemToLeave里的大多数内存,并且你仍然获得Mem to Leave,有可能存在内存泄漏,它由加载到SQL Server里某个DLL导致。参考第1部分(MTL错误) 以下查询可以用于确定SQL Server在MTL里的实际内存消耗。 1 | select sum (multi_pages_kb) from sys.dm_os_memory_clerks |
1 2 3 4 5 6 7 | SQL Server 2000 WARNING: Failed to reserve contiguous memory of Size= 65536. WARNING: Clearing procedure cache to free contiguous memory. Error: 17802 “Could not create server event thread.” SQL Server could not spawn process_loginread thread. SQL Server 2005/2008 Failed Virtual Allocate Bytes: FAIL_VIRTUAL_RESERVE 122880 |
1 2 3 4 5 | -- Create an extended stored procedure in SQL Server exec sp_addextendedproc 'HeapLeak' , 'C:\HeapLeakdll\HeapLeak.dll' -- Let us execute this Extended SP 30 times and leak memory. exec HeapLeak go 30 |
我们也将在SQL Server里启用以下跟踪标志,当有内存溢出错误时自动产生过滤dump并查看如何识别谁在泄漏内存。
1 2 3 4 5 6 7 | -- 2551 is used to enable filter dump. dbcc traceon (2551,-1) go -- 8004 is used to take memory dump on first occurrence of OOM condition dbcc traceon (8004,-1) go -- Note: Both the trace flags listed above are un-documented, So use it at your own risk and there is no guarantee that this trace flags will work in future versions of SQL Server |
当我们启用跟踪标志,我们在SQL Server里触发内存溢出错误而产生OOM内存dump。通过执行以上的扩展存储过程30次,我们从MTL泄漏了大约300MB内存。
让我们执行以下创建XML句柄的脚本。XML句柄从MTL的内存分配将不久获得内存溢出错误,因为我们执行的扩展存储过程已经泄漏了内存。 (不要在没有执行HeapLeak就直接运行以下XML脚本。以下导致OOM错误的脚本由于对每个执行创建的句柄,但是它占用SQL Server分配,所以不会帮助我们来理解如何排除由第三方DLL泄漏导致的故障)注意: 1. SQL Server内存dump将会在SQL Server错误日志目录下生成。 2. MTL的大小在32位SQL Server是256MB+Max worker threads *0.5,所以大约是384MB除非你使用-g开关修改。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | DECLARE @idoc int DECLARE @doc varchar (1000) SET @doc = '<ROOT> <Customer CustomerID="VINET" ContactName="Paul Henriot"> <Order CustomerID="VINET" EmployeeID="5" OrderDate="1996-07-04T00:00:00"> <OrderDetail OrderID="10248" ProductID="11" Quantity="12"/> <OrderDetail OrderID="10248" ProductID="42" Quantity="10"/> </Order> </Customer> <Customer CustomerID="LILAS" ContactName="Carlos Gonzlez"> <Order CustomerID="LILAS" EmployeeID="3" OrderDate="1996-08-16T00:00:00"> <OrderDetail OrderID="10283" ProductID="72" Quantity="3"/> </Order> </Customer> </ROOT>' EXEC sp_xml_preparedocument @idoc OUTPUT , @doc go 10000 |
在执行几次之后,我们会收到以下错误。
1 2 | Msg 6624, Level 16, State 12, Procedure sp_xml_preparedocument, Line 1 XML document could not be created because server memory is low. |
从下载和安装Windows Debugger来分析这个dump。
步骤1:(加载这个内存dump文件来排除故障) 打开Windbg。选择“File”菜单,选择“Open crash dump”,选择这个dump文件(SQLDump000#.mdmp) 注意:当你得到异常或断言,在你的SQL Server错误日志里将会发现SQLDump000#.mdmp。步骤2:(设置符号目录为Microsoft symbols server) 在命令窗口输入 .sympath srv*c:\\Websymbols*http://msdl.microsoft.com/download/symbols;步骤3:(从Microsoft symbols server加载符号) 输入.reload /f并点击回车。这将强制debugger立即加载所有符号。步骤4:(检查是否符号已加载) 使用debugger命令lmvm验证是否符号加载到SQL Server。 :028> lmvm sqlservr start end module name 01000000 02ba8000 sqlservr (pdb symbols) c:\websymbols\sqlservr.pdb\93AACB610C614E1EBAB0FFB42031691D2\sqlservr.pdb Loaded symbol image file: sqlservr.exe Mapped memory image file: C:\Program Files\Microsoft SQL Server\MSSQL.1\MSSQL\Binn\sqlservr.exe Image path: C:\Program Files\Microsoft SQL Server\MSSQL.1\MSSQL\Binn\sqlservr.exe Image name: sqlservr.exe Timestamp: Fri Oct 14 15:35:29 2005 (434F82E9) CheckSum: 01B73B9B ImageSize: 01BA8000 File version: 2005.90.1399.0 Product version: 9.0.1399.0 File flags: 0 (Mask 3F) File OS: 40000 NT Base File type: 1.0 App File date: 00000000.00000000 Translations: 0409.04e4 CompanyName: Microsoft Corporation ProductName: Microsoft SQL Server InternalName: SQLSERVR OriginalFilename: SQLSERVR.EXE ProductVersion: 9.00.1399.06 FileVersion: 2005.090.1399.00 FileDescription: SQL Server Windows NT LegalCopyright: Microsoft Corp. All rights reserved. LegalTrademarks: Microsoft is a registered trademark of Microsoft Corporation. Windows(TM) is a trademark of Microsoft Corporation Comments: NT INTEL X86步骤5:(!address来限制内存信息) 使用!address命令来限制dump进程的内存信息。 0:028> !address -summary ——————– Usage SUMMARY ————————– TotSize ( KB) Pct(Tots) Pct(Busy) Usage 686a7000 ( 1710748) : 81.58% 81.80% : RegionUsageIsVAD 579000 ( 5604) : 00.27% 00.00% : RegionUsageFree 4239000 ( 67812) : 03.23% 03.24% : RegionUsageImage ea6000 ( 15000) : 00.72% 00.72% : RegionUsageStack 1e000 ( 120) : 00.01% 00.01% : RegionUsageTeb 122d0000 ( 297792) : 14.20% 14.24% : RegionUsageHeap 0 ( 0) : 00.00% 00.00% : RegionUsagePageHeap 1000 ( 4) : 00.00% 00.00% : RegionUsagePeb 1000 ( 4) : 00.00% 00.00% : RegionUsageProcessParametrs 1000 ( 4) : 00.00% 00.00% : RegionUsageEnvironmentBlock Tot: 7fff0000 (2097088 KB) Busy: 7fa77000 (2091484 KB) ——————– Type SUMMARY ————————– TotSize ( KB) Pct(Tots) Usage 579000 ( 5604) : 00.27% : <free> 4239000 ( 67812) : 03.23% : MEM_IMAGE 5fc000 ( 6128) : 00.29% : MEM_MAPPED 7b242000 ( 2017544) : 96.21% : MEM_PRIVATE ——————– State SUMMARY ————————– TotSize ( KB) Pct(Tots) Usage 1b7bd000 ( 450292) : 21.47% : MEM_COMMIT 579000 ( 5604) : 00.27% : MEM_FREE 642ba000 ( 1641192) : 78.26% : MEM_RESERVE Largest free region: Base 00000000 – Size 00010000 (64 KB) 查看RegionUsageHeap,它大约是297792KB,最大的空闲区域只有64KB。我们知道SQL Server不广泛使用堆内存,因此通常SQL Server堆内存分配将不会超过几MB。在这里消耗大约290MB,因此使用MTL的其他组件很容易失败。。 让我们尝试理解为什么堆内存大约297792KB,尝试识别是否有一个模型。步骤6:(让我们使用!heap -s来显示堆内存摘要信息) 0:028> !heap -s LFH Key : 0x672ddb11 Heap Flags Reserv Commit Virt Free List UCR Virt Lock Fast (k) (k) (k) (k) length blocks cont. heap —————————————————————————– 000d0000 00000002 1024 896 896 6 1 1 0 0 L 001d0000 00008000 64 12 12 10 1 1 0 0 002c0000 00001002 1088 96 96 2 1 1 0 0 L 002e0000 00001002 64 52 52 3 2 1 0 0 L 007c0000 00001002 64 64 64 56 1 0 0 0 L 00d10000 00001002 256 24 24 8 1 1 0 0 L 340b0000 00001002 64 28 28 1 0 1 0 0 L 340c0000 00041002 256 12 12 4 1 1 0 0 L 342a0000 00000002 1024 24 24 3 1 1 0 0 L 34440000 00001002 64 48 48 40 2 1 0 0 L 61cd0000 00011002 256 12 12 4 1 1 0 0 L 61d10000 00001002 64 16 16 7 1 1 0 0 L 61d20000 00001002 64 12 12 4 1 1 0 0 L 62a90000 00001002 1024 1024 1024 1016 2 0 0 0 L 62b90000 00001002 1024 1024 1024 1016 2 0 0 0 L 62c90000 00001002 256 40 40 7 1 1 0 0 LFH 00770000 00001002 64 16 16 2 2 1 0 0 L 63820000 00001002 64 24 24 3 1 1 0 0 L 63830000 00001001 10240 10240 10240 160 21 0 0 bad 64230000 00001001 10240 10240 10240 160 21 0 0 bad 64c30000 00001001 10240 10240 10240 160 21 0 0 bad 65630000 00001001 10240 10240 10240 160 21 0 0 bad 66030000 00001001 10240 10240 10240 160 21 0 0 bad 66a30000 00001001 10240 10240 10240 160 21 0 0 bad 67430000 00001001 10240 10240 10240 160 21 0 0 bad 68130000 00001001 10240 10240 10240 160 21 0 0 bad 68b30000 00001001 10240 10240 10240 160 21 0 0 bad 69530000 00001001 10240 10240 10240 160 21 0 0 bad 69f30000 00001001 10240 10240 10240 160 21 0 0 bad 6a930000 00001001 10240 10240 10240 160 21 0 0 bad 6b330000 00001001 10240 10240 10240 160 21 0 0 bad 6bd30000 00001001 10240 10240 10240 160 21 0 0 bad 6c730000 00001001 10240 10240 10240 160 21 0 0 bad 6d130000 00001001 10240 10240 10240 160 21 0 0 bad 6db30000 00001001 10240 10240 10240 160 21 0 0 bad 6e530000 00001001 10240 10240 10240 160 21 0 0 bad 6ef30000 00001001 10240 10240 10240 160 21 0 0 bad 6f930000 00001001 10240 10240 10240 160 21 0 0 bad 70330000 00001001 10240 10240 10240 160 21 0 0 bad 70d30000 00001001 10240 10240 10240 160 21 0 0 bad 7a160000 00001001 10240 10240 10240 160 21 0 0 bad 7ab60000 00001001 10240 10240 10240 160 21 0 0 bad 7b560000 00001001 10240 10240 10240 160 21 0 0 bad 7d0d0000 00001001 10240 10240 10240 160 21 0 0 bad 7e030000 00001001 10240 10240 10240 160 21 0 0 bad 7ea30000 00001001 10240 10240 10240 160 21 0 0 bad 67f90000 00001003 256 16 16 14 1 1 0 bad 71850000 00001003 256 4 4 2 1 1 0 bad 71890000 00001003 256 4 4 2 1 1 0 bad 67fd0000 00001002 64 16 16 4 1 1 0 0 L 718d0000 00001003 256 40 40 3 1 1 0 bad 71910000 00001003 256 4 4 2 1 1 0 bad 71950000 00001003 256 4 4 2 1 1 0 bad 71990000 00001003 256 4 4 2 1 1 0 bad 67ff0000 00001002 64 16 16 4 1 1 0 0 L 719d0000 00001003 1792 1352 1352 5 2 1 0 bad 71a10000 00001003 256 4 4 2 1 1 0 bad 71a50000 00001003 256 4 4 2 1 1 0 bad 71a90000 00001002 64 16 16 1 0 1 0 0 L —————————————————————————– 如果你看以上输出,你可以清晰识别一个模型。创建了多个,每个是10MB。但是如何识别实际上是谁创建了它们?步骤7: 让我们取出其中一个10MB的堆内存,并使用带-h参数的!heap显示在这个10MB堆内存里所有的内存分配条目。 我获取的堆内存是63830000。 0:028> !heap -h 63830000 Index Address Name Debugging options enabled 19: 63830000 Segment at 63830000 to 64230000 (00a00000 bytes committed) Flags: 00001001 ForceFlags: 00000001 Granularity: 8 bytes Segment Reserve: 00100000 Segment Commit: 00002000 DeCommit Block Thres: 00000200 DeCommit Total Thres: 00002000 Total Free Size: 00005048 Max. Allocation Size: 7ffdefff Lock Variable at: 00000000 Next TagIndex: 0000 Maximum TagIndex: 0000 Tag Entries: 00000000 PsuedoTag Entries: 00000000 Virtual Alloc List: 63830050 UCR FreeList: 63830588 FreeList Usage: 00000000 00000000 00000000 00000000 FreeList[ 00 ] at 63830178: 6422de88 . 638ad7e0 Unable to read nt!_HEAP_FREE_ENTRY structure at 638ad7e0 (1 block ) Heap entries for Segment00 in Heap 63830000 63830608: 00608 . 00040 [01] – busy (40) 63830648: 00040 . 02808 [01] – busy (2800) 641b6698: 02808 . 02808 [01] – busy (2800) …………………………………… …………………………………… …………………………………… …………………………………… 步骤8:(让我们截取分配的堆内存条目之一,并尝试识别在它里面是什么) 0:028> db 641b6698 641b6698 01 05 01 05 93 01 08 00-49 61 6d 20 66 69 6c 69 ……..Iam fili 641b66a8 6e 67 20 74 68 65 20 68-65 61 70 20 66 6f 72 20 ng the heap for 641b66b8 64 65 6d 6f 20 61 74 20-4d 53 53 51 4c 57 49 4b demo at MSSQLWIK 641b66c8 49 2e 43 4f 4d 00 00 00-00 00 00 00 00 00 00 00 I.COM……….. 641b66d8 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ……………. 641b66e8 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ……………. 641b66f8 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ……………. 641b6708 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ……………. 0:028> db 63830648 63830648 01 05 08 00 89 01 08 00-49 61 6d 20 66 69 6c 69 ……..Iam fili 63830658 6e 67 20 74 68 65 20 68-65 61 70 20 66 6f 72 20 ng the heap for 63830668 64 65 6d 6f 20 61 74 20-4d 53 53 51 4c 57 49 4b demo at MSSQLWIK 63830678 49 2e 43 4f 4d 00 00 00-00 00 00 00 00 00 00 00 I.COM……….. 63830688 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ……………. 63830698 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ……………. 638306a8 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ……………. 638306b8 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ……………. 类似的,你可以dump多个堆分配来识别一个模型。 现在如果你查看dump的内存,你会看到一个字符串可能对你识别创建堆内存的DLL有帮助。在以上堆里有一个模型。所有的堆分配都有以下字符串“I am filing the heap for demo at MSSQLWIKI.COM” 注意:你可以使用L大小dump更多内存,使用db或dc命令的示例:db 63830648 L1500步骤9: 让我们使用notepad打开加载到SQL Server里用于测试的DLL,查看是否有一个字符串匹配这个模型。 是的,证明这个DLL导致了泄漏。在实践中你可能得去验证不同的堆分配来识别这个模型。 这是一个在泄漏实际发生之后从内存dump找到泄漏。找到一个模型并识别分配该内存的模块并不总是容易的,在这里你可以使用像debug diagnostic tool、UMDH等工具来跟踪泄漏。在我的下一篇博文中,我将发表如何使用Debug diagnostics tools跟踪内存泄漏。本文转自UltraSQL51CTO博客,原文链接:http://blog.51cto.com/ultrasql/1791324 ,如需转载请自行联系原作者