`
huozheleisi
  • 浏览: 1231201 次
文章分类
社区版块
存档分类
最新评论

分析、调试内存泄漏的应用程序

 
阅读更多

<!--[if !mso]> <style> v/:* {behavior:url(#default#VML);} o/:* {behavior:url(#default#VML);} w/:* {behavior:url(#default#VML);} .shape {behavior:url(#default#VML);} </style> <![endif]--><!--[if gte mso 9]><xml> <w:WordDocument> <w:View>Normal</w:View> <w:Zoom>0</w:Zoom> <w:TrackMoves>false</w:TrackMoves> <w:TrackFormatting /> <w:PunctuationKerning /> <w:DrawingGridVerticalSpacing>7.8 磅</w:DrawingGridVerticalSpacing> <w:DisplayHorizontalDrawingGridEvery>0</w:DisplayHorizontalDrawingGridEvery> <w:DisplayVerticalDrawingGridEvery>2</w:DisplayVerticalDrawingGridEvery> <w:ValidateAgainstSchemas /> <w:SaveIfXMLInvalid>false</w:SaveIfXMLInvalid> <w:IgnoreMixedContent>false</w:IgnoreMixedContent> <w:AlwaysShowPlaceholderText>false</w:AlwaysShowPlaceholderText> <w:DoNotPromoteQF /> <w:LidThemeOther>EN-US</w:LidThemeOther> <w:LidThemeAsian>ZH-CN</w:LidThemeAsian> <w:LidThemeComplexScript>X-NONE</w:LidThemeComplexScript> <w:Compatibility> <w:SpaceForUL /> <w:BalanceSingleByteDoubleByteWidth /> <w:DoNotLeaveBackslashAlone /> <w:ULTrailSpace /> <w:DoNotExpandShiftReturn /> <w:AdjustLineHeightInTable /> <w:BreakWrappedTables /> <w:SnapToGridInCell /> <w:WrapTextWithPunct /> <w:UseAsianBreakRules /> <w:DontGrowAutofit /> <w:SplitPgBreakAndParaMark /> <w:DontVertAlignCellWithSp /> <w:DontBreakConstrainedForcedTables /> <w:DontVertAlignInTxbx /> <w:Word11KerningPairs /> <w:CachedColBalance /> <w:UseFELayout /> </w:Compatibility> <w:BrowserLevel>MicrosoftInternetExplorer4</w:BrowserLevel> <m:mathPr> <m:mathFont m:val="Cambria Math" /> <m:brkBin m:val="before" /> <m:brkBinSub m:val="&#45;-" /> <m:smallFrac m:val="off" /> <m:dispDef /> <m:lMargin m:val="0" /> <m:rMargin m:val="0" /> <m:defJc m:val="centerGroup" /> <m:wrapIndent m:val="1440" /> <m:intLim m:val="subSup" /> <m:naryLim m:val="undOvr" /> </m:mathPr></w:WordDocument> </xml><![endif]--><!--[if gte mso 9]><xml> <w:LatentStyles DefLockedState="false" DefUnhideWhenUsed="true" DefSemiHidden="true" DefQFormat="false" DefPriority="99" LatentStyleCount="267"> <w:LsdException Locked="false" Priority="0" SemiHidden="false" UnhideWhenUsed="false" QFormat="true" Name="Normal" /> <w:LsdException Locked="false" Priority="9" SemiHidden="false" UnhideWhenUsed="false" QFormat="true" Name="heading 1" /> <w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 2" /> <w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 3" /> <w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 4" /> <w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 5" /> <w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 6" /> <w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 7" /> <w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 8" /> <w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 9" /> <w:LsdException Locked="false" Priority="39" Name="toc 1" /> <w:LsdException Locked="false" Priority="39" Name="toc 2" /> <w:LsdException Locked="false" Priority="39" Name="toc 3" /> <w:LsdException Locked="false" Priority="39" Name="toc 4" /> <w:LsdException Locked="false" Priority="39" Name="toc 5" /> <w:LsdException Locked="false" Priority="39" Name="toc 6" /> <w:LsdException Locked="false" Priority="39" Name="toc 7" /> <w:LsdException Locked="false" Priority="39" Name="toc 8" /> <w:LsdException Locked="false" Priority="39" Name="toc 9" /> <w:LsdException Locked="false" Priority="35" QFormat="true" Name="caption" /> <w:LsdException Locked="false" Priority="10" SemiHidden="false" UnhideWhenUsed="false" QFormat="true" Name="Title" /> <w:LsdException Locked="false" Priority="1" Name="Default Paragraph Font" /> <w:LsdException Locked="false" Priority="11" SemiHidden="false" UnhideWhenUsed="false" QFormat="true" Name="Subtitle" /> <w:LsdException Locked="false" Priority="22" SemiHidden="false" UnhideWhenUsed="false" QFormat="true" Name="Strong" /> <w:LsdException Locked="false" Priority="20" SemiHidden="false" UnhideWhenUsed="false" QFormat="true" Name="Emphasis" /> <w:LsdException Locked="false" Priority="59" SemiHidden="false" UnhideWhenUsed="false" Name="Table Grid" /> <w:LsdException Locked="false" UnhideWhenUsed="false" Name="Placeholder Text" /> <w:LsdException Locked="false" Priority="1" SemiHidden="false" UnhideWhenUsed="false" QFormat="true" Name="No Spacing" /> <w:LsdException Locked="false" Priority="60" SemiHidden="false" UnhideWhenUsed="false" Name="Light Shading" /> <w:LsdException Locked="false" Priority="61" SemiHidden="false" UnhideWhenUsed="false" Name="Light List" /> <w:LsdException Locked="false" Priority="62" SemiHidden="false" UnhideWhenUsed="false" Name="Light Grid" /> <w:LsdException Locked="false" Priority="63" SemiHidden="false" UnhideWhenUsed="false" Name="Medium Shading 1" /> <w:LsdException Locked="false" Priority="64" SemiHidden="false" UnhideWhenUsed="false" Name="Medium Shading 2" /> <w:LsdException Locked="false" Priority="65" SemiHidden="false" UnhideWhenUsed="false" Name="Medium List 1" /> <w:LsdException Locked="false" Priority="66" SemiHidden="false" UnhideWhenUsed="false" Name="Medium List 2" /> <w:LsdException Locked="false" Priority="67" SemiHidden="false" UnhideWhenUsed="false" Name="Medium Grid 1" /> <w:LsdException Locked="false" Priority="68" SemiHidden="false" UnhideWhenUsed="false" Name="Medium Grid 2" /> <w:LsdException Locked="false" Priority="69" SemiHidden="false" UnhideWhenUsed="false" Name="Medium Grid 3" /> <w:LsdException Locked="false" Priority="70" SemiHidden="false" UnhideWhenUsed="false" Name="Dark List" /> <w:LsdException Locked="false" Priority="71" SemiHidden="false" UnhideWhenUsed="false" Name="Colorful Shading" /> <w:LsdException Locked="false" Priority="72" SemiHidden="false" UnhideWhenUsed="false" Name="Colorful List" /> <w:LsdException Locked="false" Priority="73" SemiHidden="false" UnhideWhenUsed="false" Name="Colorful Grid" /> <w:LsdException Locked="false" Priority="60" SemiHidden="false" UnhideWhenUsed="false" Name="Light Shading Accent 1" /> <w:LsdException Locked="false" Priority="61" SemiHidden="false" UnhideWhenUsed="false" Name="Light List Accent 1" /> <w:LsdException Locked="false" Priority="62" SemiHidden="false" UnhideWhenUsed="false" Name="Light Grid Accent 1" /> <w:LsdException Locked="false" Priority="63" SemiHidden="false" UnhideWhenUsed="false" Name="Medium Shading 1 Accent 1" /> <w:LsdException Locked="false" Priority="64" SemiHidden="false" UnhideWhenUsed="false" Name="Medium Shading 2 Accent 1" /> <w:LsdException Locked="false" Priority="65" SemiHidden="false" UnhideWhenUsed="false" Name="Medium List 1 Accent 1" /> <w:LsdException Locked="false" UnhideWhenUsed="false" Name="Revision" /> <w:LsdException Locked="false" Priority="34" SemiHidden="false" UnhideWhenUsed="false" QFormat="true" Name="List Paragraph" /> <w:LsdException Locked="false" Priority="29" SemiHidden="false" UnhideWhenUsed="false" QFormat="true" Name="Quote" /> <w:LsdException Locked="false" Priority="30" SemiHidden="false" UnhideWhenUsed="false" QFormat="true" Name="Intense Quote" /> <w:LsdException Locked="false" Priority="66" SemiHidden="false" UnhideWhenUsed="false" Name="Medium List 2 Accent 1" /> <w:LsdException Locked="false" Priority="67" SemiHidden="false" UnhideWhenUsed="false" Name="Medium Grid 1 Accent 1" /> <w:LsdException Locked="false" Priority="68" SemiHidden="false" UnhideWhenUsed="false" Name="Medium Grid 2 Accent 1" /> <w:LsdException Locked="false" Priority="69" SemiHidden="false" UnhideWhenUsed="false" Name="Medium Grid 3 Accent 1" /> <w:LsdException Locked="false" Priority="70" SemiHidden="false" UnhideWhenUsed="false" Name="Dark List Accent 1" /> <w:LsdException Locked="false" Priority="71" SemiHidden="false" UnhideWhenUsed="false" Name="Colorful Shading Accent 1" /> <w:LsdException Locked="false" Priority="72" SemiHidden="false" UnhideWhenUsed="false" Name="Colorful List Accent 1" /> <w:LsdException Locked="false" Priority="73" SemiHidden="false" UnhideWhenUsed="false" Name="Colorful Grid Accent 1" /> <w:LsdException Locked="false" Priority="60" SemiHidden="false" UnhideWhenUsed="false" Name="Light Shading Accent 2" /> <w:LsdException Locked="false" Priority="61" SemiHidden="false" UnhideWhenUsed="false" Name="Light List Accent 2" /> <w:LsdException Locked="false" Priority="62" SemiHidden="false" UnhideWhenUsed="false" Name="Light Grid Accent 2" /> <w:LsdException Locked="false" Priority="63" SemiHidden="false" UnhideWhenUsed="false" Name="Medium Shading 1 Accent 2" /> <w:LsdException Locked="false" Priority="64" SemiHidden="false" UnhideWhenUsed="false" Name="Medium Shading 2 Accent 2" /> <w:LsdException Locked="false" Priority="65" SemiHidden="false" UnhideWhenUsed="false" Name="Medium List 1 Accent 2" /> <w:LsdException Locked="false" Priority="66" SemiHidden="false" UnhideWhenUsed="false" Name="Medium List 2 Accent 2" /> <w:LsdException Locked="false" Priority="67" SemiHidden="false" UnhideWhenUsed="false" Name="Medium Grid 1 Accent 2" /> <w:LsdException Locked="false" Priority="68" SemiHidden="false" UnhideWhenUsed="false" Name="Medium Grid 2 Accent 2" /> <w:LsdException Locked="false" Priority="69" SemiHidden="false" UnhideWhenUsed="false" Name="Medium Grid 3 Accent 2" /> <w:LsdException Locked="false" Priority="70" SemiHidden="false" UnhideWhenUsed="false" Name="Dark List Accent 2" /> <w:LsdException Locked="false" Priority="71" SemiHidden="false" UnhideWhenUsed="false" Name="Colorful Shading Accent 2" /> <w:LsdException Locked="false" Priority="72" SemiHidden="false" UnhideWhenUsed="false" Name="Colorful List Accent 2" /> <w:LsdException Locked="false" Priority="73" SemiHidden="false" UnhideWhenUsed="false" Name="Colorful Grid Accent 2" /> <w:LsdException Locked="false" Priority="60" SemiHidden="false" UnhideWhenUsed="false" Name="Light Shading Accent 3" /> <w:LsdException Locked="false" Priority="61" SemiHidden="false" UnhideWhenUsed="false" Name="Light List Accent 3" /> <w:LsdException Locked="false" Priority="62" SemiHidden="false" UnhideWhenUsed="false" Name="Light Grid Accent 3" /> <w:LsdException Locked="false" Priority="63" SemiHidden="false" UnhideWhenUsed="false" Name="Medium Shading 1 Accent 3" /> <w:LsdException Locked="false" Priority="64" SemiHidden="false" UnhideWhenUsed="false" Name="Medium Shading 2 Accent 3" /> <w:LsdException Locked="false" Priority="65" SemiHidden="false" UnhideWhenUsed="false" Name="Medium List 1 Accent 3" /> <w:LsdException Locked="false" Priority="66" SemiHidden="false" UnhideWhenUsed="false" Name="Medium List 2 Accent 3" /> <w:LsdException Locked="false" Priority="67" SemiHidden="false" UnhideWhenUsed="false" Name="Medium Grid 1 Accent 3" /> <w:LsdException Locked="false" Priority="68" SemiHidden="false" UnhideWhenUsed="false" Name="Medium Grid 2 Accent 3" /> <w:LsdException Locked="false" Priority="69" SemiHidden="false" UnhideWhenUsed="false" Name="Medium Grid 3 Accent 3" /> <w:LsdException Locked="false" Priority="70" SemiHidden="false" UnhideWhenUsed="false" Name="Dark List Accent 3" /> <w:LsdException Locked="false" Priority="71" SemiHidden="false" UnhideWhenUsed="false" Name="Colorful Shading Accent 3" /> <w:LsdException Locked="false" Priority="72" SemiHidden="false" UnhideWhenUsed="false" Name="Colorful List Accent 3" /> <w:LsdException Locked="false" Priority="73" SemiHidden="false" UnhideWhenUsed="false" Name="Colorful Grid Accent 3" /> <w:LsdException Locked="false" Priority="60" SemiHidden="false" UnhideWhenUsed="false" Name="Light Shading Accent 4" /> <w:LsdException Locked="false" Priority="61" SemiHidden="false" UnhideWhenUsed="false" Name="Light List Accent 4" /> <w:LsdException Locked="false" Priority="62" SemiHidden="false" UnhideWhenUsed="false" Name="Light Grid Accent 4" /> <w:LsdException Locked="false" Priority="63" SemiHidden="false" UnhideWhenUsed="false" Name="Medium Shading 1 Accent 4" /> <w:LsdException Locked="false" Priority="64" SemiHidden="false" UnhideWhenUsed="false" Name="Medium Shading 2 Accent 4" /> <w:LsdException Locked="false" Priority="65" SemiHidden="false" UnhideWhenUsed="false" Name="Medium List 1 Accent 4" /> <w:LsdException Locked="false" Priority="66" SemiHidden="false" UnhideWhenUsed="false" Name="Medium List 2 Accent 4" /> <w:LsdException Locked="false" Priority="67" SemiHidden="false" UnhideWhenUsed="false" Name="Medium Grid 1 Accent 4" /> <w:LsdException Locked="false" Priority="68" SemiHidden="false" UnhideWhenUsed="false" Name="Medium Grid 2 Accent 4" /> <w:LsdException Locked="false" Priority="69" SemiHidden="false" UnhideWhenUsed="false" Name="Medium Grid 3 Accent 4" /> <w:LsdException Locked="false" Priority="70" SemiHidden="false" UnhideWhenUsed="false" Name="Dark List Accent 4" /> <w:LsdException Locked="false" Priority="71" SemiHidden="false" UnhideWhenUsed="false" Name="Colorful Shading Accent 4" /> <w:LsdException Locked="false" Priority="72" SemiHidden="false" UnhideWhenUsed="false" Name="Colorful List Accent 4" /> <w:LsdException Locked="false" Priority="73" SemiHidden="false" UnhideWhenUsed="false" Name="Colorful Grid Accent 4" /> <w:LsdException Locked="false" Priority="60" SemiHidden="false" UnhideWhenUsed="false" Name="Light Shading Accent 5" /> <w:LsdException Locked="false" Priority="61" SemiHidden="false" UnhideWhenUsed="false" Name="Light List Accent 5" /> <w:LsdException Locked="false" Priority="62" SemiHidden="false" UnhideWhenUsed="false" Name="Light Grid Accent 5" /> <w:LsdException Locked="false" Priority="63" SemiHidden="false" UnhideWhenUsed="false" Name="Medium Shading 1 Accent 5" /> <w:LsdException Locked="false" Priority="64" SemiHidden="false" UnhideWhenUsed="false" Name="Medium Shading 2 Accent 5" /> <w:LsdException Locked="false" Priority="65" SemiHidden="false" UnhideWhenUsed="false" Name="Medium List 1 Accent 5" /> <w:LsdException Locked="false" Priority="66" SemiHidden="false" UnhideWhenUsed="false" Name="Medium List 2 Accent 5" /> <w:LsdException Locked="false" Priority="67" SemiHidden="false" UnhideWhenUsed="false" Name="Medium Grid 1 Accent 5" /> <w:LsdException Locked="false" Priority="68" SemiHidden="false" UnhideWhenUsed="false" Name="Medium Grid 2 Accent 5" /> <w:LsdException Locked="false" Priority="69" SemiHidden="false" UnhideWhenUsed="false" Name="Medium Grid 3 Accent 5" /> <w:LsdException Locked="false" Priority="70" SemiHidden="false" UnhideWhenUsed="false" Name="Dark List Accent 5" /> <w:LsdException Locked="false" Priority="71" SemiHidden="false" UnhideWhenUsed="false" Name="Colorful Shading Accent 5" /> <w:LsdException Locked="false" Priority="72" SemiHidden="false" UnhideWhenUsed="false" Name="Colorful List Accent 5" /> <w:LsdException Locked="false" Priority="73" SemiHidden="false" UnhideWhenUsed="false" Name="Colorful Grid Accent 5" /> <w:LsdException Locked="false" Priority="60" SemiHidden="false" UnhideWhenUsed="false" Name="Light Shading Accent 6" /> <w:LsdException Locked="false" Priority="61" SemiHidden="false" UnhideWhenUsed="false" Name="Light List Accent 6" /> <w:LsdException Locked="false" Priority="62" SemiHidden="false" UnhideWhenUsed="false" Name="Light Grid Accent 6" /> <w:LsdException Locked="false" Priority="63" SemiHidden="false" UnhideWhenUsed="false" Name="Medium Shading 1 Accent 6" /> <w:LsdException Locked="false" Priority="64" SemiHidden="false" UnhideWhenUsed="false" Name="Medium Shading 2 Accent 6" /> <w:LsdException Locked="false" Priority="65" SemiHidden="false" UnhideWhenUsed="false" Name="Medium List 1 Accent 6" /> <w:LsdException Locked="false" Priority="66" SemiHidden="false" UnhideWhenUsed="false" Name="Medium List 2 Accent 6" /> <w:LsdException Locked="false" Priority="67" SemiHidden="false" UnhideWhenUsed="false" Name="Medium Grid 1 Accent 6" /> <w:LsdException Locked="false" Priority="68" SemiHidden="false" UnhideWhenUsed="false" Name="Medium Grid 2 Accent 6" /> <w:LsdException Locked="false" Priority="69" SemiHidden="false" UnhideWhenUsed="false" Name="Medium Grid 3 Accent 6" /> <w:LsdException Locked="false" Priority="70" SemiHidden="false" UnhideWhenUsed="false" Name="Dark List Accent 6" /> <w:LsdException Locked="false" Priority="71" SemiHidden="false" UnhideWhenUsed="false" Name="Colorful Shading Accent 6" /> <w:LsdException Locked="false" Priority="72" SemiHidden="false" UnhideWhenUsed="false" Name="Colorful List Accent 6" /> <w:LsdException Locked="false" Priority="73" SemiHidden="false" UnhideWhenUsed="false" Name="Colorful Grid Accent 6" /> <w:LsdException Locked="false" Priority="19" SemiHidden="false" UnhideWhenUsed="false" QFormat="true" Name="Subtle Emphasis" /> <w:LsdException Locked="false" Priority="21" SemiHidden="false" UnhideWhenUsed="false" QFormat="true" Name="Intense Emphasis" /> <w:LsdException Locked="false" Priority="31" SemiHidden="false" UnhideWhenUsed="false" QFormat="true" Name="Subtle Reference" /> <w:LsdException Locked="false" Priority="32" SemiHidden="false" UnhideWhenUsed="false" QFormat="true" Name="Intense Reference" /> <w:LsdException Locked="false" Priority="33" SemiHidden="false" UnhideWhenUsed="false" QFormat="true" Name="Book Title" /> <w:LsdException Locked="false" Priority="37" Name="Bibliography" /> <w:LsdException Locked="false" Priority="39" QFormat="true" Name="TOC Heading" /> </w:LatentStyles> </xml><![endif]--> <!-- /* Font Definitions */ @font-face {font-family:Wingdings; panose-1:5 0 0 0 0 0 0 0 0 0; mso-font-charset:2; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:0 268435456 0 0 -2147483648 0;} @font-face {font-family:宋体; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-alt:SimSun; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:3 135135232 16 0 262145 0;} @font-face {font-family:"Cambria Math"; panose-1:2 4 5 3 5 4 6 3 2 4; mso-font-charset:0; mso-generic-font-family:roman; mso-font-pitch:variable; mso-font-signature:-1610611985 1107304683 0 0 159 0;} @font-face {font-family:Calibri; panose-1:2 15 5 2 2 2 4 3 2 4; mso-font-charset:0; mso-generic-font-family:swiss; mso-font-pitch:variable; mso-font-signature:-1610611985 1073750139 0 0 159 0;} @font-face {font-family:"/@宋体"; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:3 135135232 16 0 262145 0;} /* Style Definitions */ p.MsoNormal, li.MsoNormal, div.MsoNormal {mso-style-unhide:no; mso-style-qformat:yes; mso-style-parent:""; margin:0cm; margin-bottom:.0001pt; text-align:justify; text-justify:inter-ideograph; mso-pagination:none; font-size:10.5pt; mso-bidi-font-size:11.0pt; font-family:"Calibri","sans-serif"; mso-ascii-font-family:Calibri; mso-ascii-theme-font:minor-latin; mso-fareast-font-family:宋体; mso-fareast-theme-font:minor-fareast; mso-hansi-font-family:Calibri; mso-hansi-theme-font:minor-latin; mso-bidi-font-family:"Times New Roman"; mso-bidi-theme-font:minor-bidi; mso-font-kerning:1.0pt;} .MsoChpDefault {mso-style-type:export-only; mso-default-props:yes; mso-bidi-font-family:"Times New Roman"; mso-bidi-theme-font:minor-bidi;} /* Page Definitions */ @page {mso-page-border-surround-header:no; mso-page-border-surround-footer:no;} @page Section1 {size:595.3pt 841.9pt; margin:72.0pt 90.0pt 72.0pt 90.0pt; mso-header-margin:42.55pt; mso-footer-margin:49.6pt; mso-paper-source:0; layout-grid:15.6pt;} div.Section1 {page:Section1;} /* List Definitions */ @list l0 {mso-list-id:1745568359; mso-list-template-ids:1176537200;} @list l0:level1 {mso-level-number-format:bullet; mso-level-text:; mso-level-tab-stop:36.0pt; mso-level-number-position:left; text-indent:-18.0pt; mso-ansi-font-size:10.0pt; font-family:Symbol;} ol {margin-bottom:0cm;} ul {margin-bottom:0cm;} --> <!--[if gte mso 10]> <style> /* Style Definitions */ table.MsoNormalTable {mso-style-name:普通表格; mso-tstyle-rowband-size:0; mso-tstyle-colband-size:0; mso-style-noshow:yes; mso-style-priority:99; mso-style-qformat:yes; mso-style-parent:""; mso-padding-alt:0cm 5.4pt 0cm 5.4pt; mso-para-margin:0cm; mso-para-margin-bottom:.0001pt; mso-pagination:widow-orphan; font-size:10.5pt; mso-bidi-font-size:11.0pt; font-family:"Calibri","sans-serif"; mso-ascii-font-family:Calibri; mso-ascii-theme-font:minor-latin; mso-hansi-font-family:Calibri; mso-hansi-theme-font:minor-latin; mso-font-kerning:1.0pt;} </style> <![endif]-->项目组的一个window service (7×24)出现 OutOfMemory异常,一直找不到原因。尝试过各种方法。

1、去掉方法和类中 static ,以便资源释放;

2、将无线等待的“死”循环while(true){...} 更改为 心跳触发;

3、将核心类实现 IDisposiable 接口,以便更好释放资源;

等等,其他很多优化的操作,均不见效。这个问题也成为项目组的一个心病。

一个偶然的机会,在对比服务器800M 和900M的.dmp文件时,无意间看到 !dumpdomain -stat 中,AppDomain的Assembly 居然有1800、2000个(My God),为啥这里会出现这么多的Assembly。

经 过咨询项目老大后才知道,项目中有一个动态编译c#的模块,编译时会生成内存中的Assembly,而默认情况下,这个Assembly是直接加载在 AppDomain中,除非 AppDomain卸载,否则Assembly会一直随着应用程序域的存在而持续存在,虽然这个过程仅占用0.6Kbytes的内存,但是对于 7*24小时的服务来说,OutOfMemory只是时间的问题而已。剩下的就是想办法如何解决这个问题了。

ps:贴一篇MS的文章,在这里终于找到了佐证。

原文:http://msdn.microsoft.com/zh-cn/magazine/cc163491.aspx

调试内存泄漏的应用程序

发现并防止托管代码中出现内存泄漏

James Kovacs

本文讨论:

  • 理解托管应用程序中的内存泄漏问题
  • .NET 应用程序中所用的非托管内存
  • 帮助 .NET 垃圾收集器发挥应有功效

本文使用了以下技术:
.NET Framework

下载本文中所用的代码: MemoryLeaks2007_01.exe (163 KB)
浏览在线代码

<!--[if gte vml 1]><v:shapetype id="_x0000_t75" coordsize="21600,21600" o:spt="75" o:preferrelative="t" path="m@4@5l@4@11@9@11@9@5xe" filled="f" stroked="f"> <v:stroke joinstyle="miter" /> <v:formulas> <v:f eqn="if lineDrawn pixelLineWidth 0" /> <v:f eqn="sum @0 1 0" /> <v:f eqn="sum 0 0 @1" /> <v:f eqn="prod @2 1 2" /> <v:f eqn="prod @3 21600 pixelWidth" /> <v:f eqn="prod @3 21600 pixelHeight" /> <v:f eqn="sum @0 0 1" /> <v:f eqn="prod @6 1 2" /> <v:f eqn="prod @7 21600 pixelWidth" /> <v:f eqn="sum @8 21600 0" /> <v:f eqn="prod @7 21600 pixelHeight" /> <v:f eqn="sum @10 21600 0" /> </v:formulas> <v:path o:extrusionok="f" gradientshapeok="t" o:connecttype="rect" /> <o:lock v:ext="edit" aspectratio="t" /> </v:shapetype><v:shape id="ctl00_mainContentContainer_cpe141981_i" o:spid="_x0000_i1040" type="#_x0000_t75" alt="http://i.msdn.microsoft.com/Global/Images/clear.gif" style='width:.75pt;height:.75pt;visibility:visible;mso-wrap-style:square'> <v:imagedata src="file:///C:/DOCUME~1/DENGSH~1/LOCALS~1/Temp/msohtmlclip1/01/clip_image001.gif" o:title="clear" /> </v:shape><![endif]--><!--[if !vml]-->http://i.msdn.microsoft.com/Global/Images/clear.gif<!--[endif]-->目录

.NET 应用程序中的内存
检测泄漏
堆栈内存泄漏
非托管堆内存泄漏
泄漏”托管堆内存
总结

一提到托管代码中出现内存泄漏,很多开发人员的第一反应都认为这是不可能的。毕竟垃圾收集器 (GC) 会负责管理所有的内存,没错吧?但要知道,垃圾收集器只处理托管内存。基于 Microsoft® .NET Framework 的应用程序中大量使用了非托管内存,这些非托管内存既可以被公共语言运行库 (CLR) 使用,也可以在与非托管代码进行互操作时被程序员显式使用。在某些情况下,垃圾管理器似乎在逃避自己的职责,没有对托管内存进行有效处理。这通常是由于不 易察觉的(也可能是非常明显的)编程错误妨碍了垃圾收集器的正常工作而造成的。作为经常与内存打交道的程序员,我们仍需要检查自己的应用程序,确保它们不 会发生内存泄漏并能够合理有效地使用所需内存。

.NET 应用程序中的内存

您大概已经知道,.NET 应用程序中要使用多种类型的内存,包括:堆栈、非托管堆和托管堆。这里我们需要简单回顾一下。

堆栈 堆栈用于存储应用程序执行过程中的局部变量、方法参数、返回值和其他临时值。堆栈按照每个线程进行分配,并作为每个线程完成其工作的一个暂存区。垃圾收集 器并不负责清理堆栈,因为为方法调用预留的堆栈会在方法返回时被自动清理。但是请注意,垃圾收集器知道在堆栈上存储的对象的引用。当对象在一种方法中被实 例化时,该对象的引用(32 位或 64 位整型值,取决于平台类型)将保留在堆栈中,而对象自身却存储于托管堆中,并在变量超出范围时被垃圾收集器收集。

非托管堆 非托管堆用于运行时数据结构、方法表、Microsoft 中间语言 (MSIL)JITed 代码等。非托管代码根据对象的实例化方式将其分配在非托管堆或堆栈上。托管代码可通过调用非托管的 Win32® API 或实例化 COM 对象来直接分配非托管堆内存。CLR 出于自身的数据结构和代码原因广泛地使用非托管堆。

托管堆 托管堆是用于分配托管对象的区域,同时也是垃圾收集器的域。CLR 使用分代压缩垃圾收集器。垃圾收集器之所以称为分代式,是由于它将垃圾收集后保留下来的对象按生存时间进行划分,这样做有助于提高性能。所有版本的 .NET Framework 都采用三代分代方法:第 0 代、第 1 代和第 2 代(从年轻代到年老代)。垃圾收集器之所以称为压缩式,是因为它将对象重新定位于托管堆上,从而能够消除漏洞并保持可用内存的连续性。移动大型对象的开销 很高,因此垃圾收集器将这些大型对象分配在独立的且不会压缩的大型对象堆上。有关托管堆和垃圾收集器的详细信息,请参阅 Jeffrey Richter 所著的分为两部分的系列文章垃圾收集器:Microsoft .NET Framework 中的自动内存管理垃圾收集器 第 2 部分:Microsoft .NET Framework 中的自动内存管理。虽然该文的写作是基于 .NET Framework 1.0,而且 .NET 垃圾收集器已经有所改进,但是其中的核心思想与 1.1 版或 2.0 版是保持一致的。

检测泄漏

很 多迹象能够表明应用程序正在发生内存泄漏。或许应用程序正在引发 OutOfMemoryException。或许应用程序因启动了虚拟内存与硬盘的交换而变得响应迟缓。或许出现任务管理器中内存的使用率逐渐(也可能突 然地)上升。当怀疑应用程序发生内存泄漏时,必须首先确定是哪种类型的内存发生泄漏,以便您将调试工作的重点放在合适的区域。使用 PerfMon 来检查用于应用程序的下列性能计数器:Process/Private Bytes.NET CLR Memory/# Bytes in All Heaps .NET CLR LocksAndThreads/# of current logical ThreadsProcess/Private Bytes 计数器用于报告系统中专门为某一进程分配而无法与其他进程共享的所有内存。.NET CLR Memory/# Bytes in All Heaps 计数器报告第 0 代、第 1 代、第 2 代和大型对象堆的合计大小。.NET CLR LocksAndThreads/# of current logical Threads 计数器报告 AppDomain 中逻辑线程的数量。如果应用程序的逻辑线程计数出现意想不到的增大,则表明线程堆栈发生泄漏。如果 Private Bytes 增大,而 # Bytes in All Heaps 保持不变,则表明非托管内存发生泄漏。如果上述两个计数器均有所增加,则表明托管堆中的内存消耗在增长。

堆栈内存泄漏

虽 然有可能出现堆栈空间不足而导致在受托管的情况下引发 StackOverflowException 异常,但是方法调用期间使用的任何堆栈空间都会在该方法返回后被回收。因此,实际上只有在两种情况下才会发生堆栈空间泄漏。一种情况是进行一种极其耗费堆 栈资源并且从不返回的方法调用,从而使关联的堆栈帧无法得到释放。另一种情况是发生线程泄漏,从而使线程的整个堆栈发生泄漏。如果应用程序为了执行后台工 作而创建了工作线程,但却忽略了正常终止这些进程,则可引起线程泄漏。默认情况下,最新桌面机和服务器版的 Windows® 堆栈大小均为 1MB。因此如果应用程序的 Process/Private Bytes 定期增大 1MB,同时 .NET CLR LocksAndThreads/# of current logical Threads 也相应增大,那么罪魁祸首很可能是线程堆栈泄漏。 1 显示了(恶意的)多线程逻辑导致的不正确的线程清理示例。

<!--[if gte vml 1]><v:shape id="ctl00_mainContentContainer_cpe141982_i" o:spid="_x0000_i1039" type="#_x0000_t75" alt="http://i.msdn.microsoft.com/Global/Images/clear.gif" style='width:.75pt;height:.75pt;visibility:visible;mso-wrap-style:square'> <v:imagedata src="file:///C:/DOCUME~1/DENGSH~1/LOCALS~1/Temp/msohtmlclip1/01/clip_image001.gif" o:title="clear" /> </v:shape><![endif]--><!--[if !vml]-->http://i.msdn.microsoft.com/Global/Images/clear.gif<!--[endif]-->Figure1清理错误线程

<!--[if gte vml 1]><v:shape id="图片_x0020_35" o:spid="_x0000_i1038" type="#_x0000_t75" alt="http://i.msdn.microsoft.com/Global/Images/clear.gif" href="javascript:CopyCode('ctl00_mainContentContainer_ctl07');" title="&quot;复制代码&quot;" style='width:6.75pt;height:6.75pt;visibility:visible;mso-wrap-style:square' o:button="t"> <v:imagedata src="file:///C:/DOCUME~1/DENGSH~1/LOCALS~1/Temp/msohtmlclip1/01/clip_image001.gif" o:title="clear" /> </v:shape><![endif]--><!--[if !vml]-->http://i.msdn.microsoft.com/Global/Images/clear.gif<!--[endif]-->复制代码

using System;

using System.Threading;

namespace MsdnMag.ThreadForker {

class Program {

static void Main() {

while(true) {

Console.WriteLine(

"Press <ENTER> to fork another thread...");

Console.ReadLine();

Thread t = new Thread(new ThreadStart(ThreadProc));

t.Start();

}

}

static void ThreadProc() {

Console.WriteLine("Thread #{0} started...",

Thread.CurrentThread.ManagedThreadId);

// Block until current thread terminates - i.e. wait forever

Thread.CurrentThread.Join();

}

}

}

当 一个线程启动后会显示其线程 ID,然后尝试自联接。联接会导致调用线程停止等待另一线程的终止。这样该线程就会陷入一个类似于先有鸡还是先有蛋的尴尬局面之中线程要等待自身的终止。在任务管理器下查看该程序,会发现每次按 <Enter> 时,其内存使用率会增长 1MB(即线程堆栈的大小)。

每 次经过循环时,Thread 对象的引用都会被删除,但垃圾收集器并未回收分配给线程堆栈的内存。托管线程的生存期并不依赖于创建它的 Thread 对象。如果您只是因为丢失了所有与 Thread 对象相关联的引用而不希望垃圾收集器将一个仍在运行的进程终止,这种不依赖性是非常有好处的。由此可见,垃圾收集器只是收集 Thread 对象,而非实际托管的线程。只有在其 ThreadProc 返回后或者自身被直接终止的情况下,托管线程才会退出(其线程堆栈的内存不会释放)。因此,如果托管线程的终止方式不正确,分配至其线程堆栈的内存就会发 生泄漏。

非托管堆内存泄漏

如果总的内存使用率增加,而逻辑线程计数和托管堆内存并未增加,则表明非托管堆出现内存泄漏。我们将对导致非托管堆中出现内存泄漏的一些常见原因进行分析,其中包括与非托管代码进行互操作、终结器被终止以及程序集泄漏。

与 非托管代码进行互操作:这是内存泄漏的起因之一,涉及到与非托管代码的互操作,例如在 COM Interop 中通过 P/Invoke COM 对象使用 C 样式的 DLL。垃圾收集器无法识别非托管内存,而正是在托管代码的编写过程中错误地使用了非托管内存,才导致内存出现泄漏。如果应用程序与非托管代码进行互操 作,要逐步查看代码并检查非托管调用前后内存的使用情况,以验证内存是否被正确回收。如果内存未被正确回收,则使用传统的调试方法在非托管组件中查找泄 漏。

终 结器被终止:当一个对象的终结器未被调用,并且其中含有用于清理对象所分配的非托管内存的代码时,会造成隐性泄漏。在正常情况下,终结器都将被调用,但是 CLR 不会对此提供任何保证。虽然未来可能会有所变化,但是目前的 CLR 版本仅使用一个终结器线程。请考虑这样一种情况,运行不正常的终结器试图将信息记录到脱机的数据库。如果该运行不正常的终结器反复尝试对数据库进行错误的 访问而从不返回,则运行正常的终结器将永远没有机会运行。该问题会不时出现,因为这取决于终结器在终结队列中的位置以及其他终结器采取何种行为。

AppDomain 拆开时,CLR 将通过运行所有终结器来尝试清理终结器队列。被延迟的终结器可阻止 CLR 完成 AppDomain 拆开。为此,CLR 在该进程上做了超时操作,随后将停止该终止进程。但是这并不意味着世界末日已经来临。因为通常情况下,大多数应用程序只有一个 AppDomain,而只有进程被关闭才会导致 AppDomain 的拆开。当操作系统进程被关闭,操作系统会对该进程资源进行恢复。但不幸的是,在诸如 ASP.NET SQL Server™ 之类的宿主情况下,AppDomain 的拆开并不意味着宿主进程的结束。另一个 AppDomain 会在同一进程中启动。任何因自身终结器未运行而被组件泄漏的非托管内存都将继续保持未引用状态,无法被访问,并且占用一定空间。因为内存的泄漏会随着时间 的推移越来越严重,所以这将带来灾难性的后果。

.NET 1.x 中,唯一的解决方法是结束并重新启动该进程。.NET Framework 2.0 中引入了关键的终结器,指明在 AppDomain 关闭期间,终结器将清理非托管资源并必须获得运行的机会。有关详细信息,请参阅 Stephen Toub 的文章:用 .NET Framework 的可靠性功能确保代码稳定运行

程序集泄漏:程序集泄漏相对来说要常见一些。一旦程序集被加载,它只有在 AppDomain 被卸载的情况下才能被卸载。程序集泄漏也正是由此引发的。大多数情况下,除非程序集是被动态生成并加载的(唉,俺就遇到这个10亿份之一了),否则这根本不算个问题。下面我们就来看一看动态 代码生成造成的泄漏,特别要详细分析 XmlSerializer 的泄漏。

动态代码生成有时会泄漏我们需要动态生成代码。也许应用程序具有与 Microsoft Office 相似的宏脚本编写接口来提高其扩展性。也许某个债券定价引擎需要动态加载定价规则,以便最终用户能够创建自己的债券类型。也许应用程序是用于 Python 的动态语言运行库/编译器。在很多情况下,出于性能方面的考虑,最好是通过编写宏、定价规则或 MSLI 代码来解决问题。您可以使用 System.CodeDom 来动态生成 MSLI

2 中的代码可在内存中动态生成一个程序集。该程序集可被重复调用而不会出现问题。遗憾的是,一旦宏、定价规则或代码有所改变,就必须重新生成新的动态程序 集。原有的程序集将不再使用,但是却无法从内存中清除,加载有程序集的 AppDomain 也无法被卸载。其代码、JITed 方法和其他运行时数据结构所用的非托管堆内存已经被泄漏。(托管内存也在动态生成的类上以任意静态字段的形式被泄漏。)要检测到这一问题,我们尚无良方妙 计。如果您正使用 System.CodeDom 动态地生成 MSLI,请检查是否重新生成了代码。如果有代码生成,那么您的非托管堆内存正在发生泄漏。

<!--[if gte vml 1]><v:shape id="ctl00_mainContentContainer_cpe141983_i" o:spid="_x0000_i1037" type="#_x0000_t75" alt="http://i.msdn.microsoft.com/Global/Images/clear.gif" style='width:.75pt;height:.75pt;visibility:visible;mso-wrap-style:square'> <v:imagedata src="file:///C:/DOCUME~1/DENGSH~1/LOCALS~1/Temp/msohtmlclip1/01/clip_image001.gif" o:title="clear" /> </v:shape><![endif]--><!--[if !vml]-->http://i.msdn.microsoft.com/Global/Images/clear.gif<!--[endif]-->Figure2在内存中动态生成程序集

<!--[if gte vml 1]><v:shape id="图片_x0020_37" o:spid="_x0000_i1036" type="#_x0000_t75" alt="http://i.msdn.microsoft.com/Global/Images/clear.gif" href="javascript:CopyCode('ctl00_mainContentContainer_ctl11');" title="&quot;复制代码&quot;" style='width:6.75pt;height:6.75pt;visibility:visible;mso-wrap-style:square' o:button="t"> <v:imagedata src="file:///C:/DOCUME~1/DENGSH~1/LOCALS~1/Temp/msohtmlclip1/01/clip_image001.gif" o:title="clear" /> </v:shape><![endif]--><!--[if !vml]-->http://i.msdn.microsoft.com/Global/Images/clear.gif<!--[endif]-->复制代码

CodeCompileUnit program = new CodeCompileUnit();

CodeNamespace ns = new

CodeNamespace("MsdnMag.MemoryLeaks.CodeGen.CodeDomGenerated");

ns.Imports.Add(new CodeNamespaceImport("System"));

program.Namespaces.Add(ns);

CodeTypeDeclaration class1 = new CodeTypeDeclaration("CodeDomHello");

ns.Types.Add(class1);

CodeEntryPointMethod start = new CodeEntryPointMethod();

start.ReturnType = new CodeTypeReference(typeof(void));

CodeMethodInvokeExpression cs1 = new CodeMethodInvokeExpression(

new CodeTypeReferenceExpression("System.Console"), "WriteLine",

new CodePrimitiveExpression("Hello, World!"));

start.Statements.Add(cs1);

class1.Members.Add(start);

CSharpCodeProvider provider = new CSharpCodeProvider();

CompilerResults results = provider.CompileAssemblyFromDom(

new CompilerParameters(), program);

目 前有两种主要方法可解决这一问题。第一种方法是将动态生成的 MSLI 加载到子 AppDomain 中。子 AppDomain 能够在所生成的代码发生改变时被卸载,并运行一个新的子 AppDomain 来托管更新后的 MSLI。这种方法在所有版本的 .NET Framework 中都是行之有效的。

.NET Framework 2.0 中还引入了另外一种叫做轻量级代码生成的方法,也称动态方法。使用 DynamicMethod 可以显式发出 MSLI 的操作码来定义方法体,然后可以直接通过 DynamicMethod.Invoke 或通过合适的委托来调用 DynamicMethod

<!--[if gte vml 1]><v:shape id="图片_x0020_38" o:spid="_x0000_i1035" type="#_x0000_t75" alt="http://i.msdn.microsoft.com/Global/Images/clear.gif" href="javascript:CopyCode('ctl00_mainContentContainer_ctl12');" title="&quot;复制代码&quot;" style='width:6.75pt;height:6.75pt;visibility:visible;mso-wrap-style:square' o:button="t"> <v:imagedata src="file:///C:/DOCUME~1/DENGSH~1/LOCALS~1/Temp/msohtmlclip1/01/clip_image001.gif" o:title="clear" /> </v:shape><![endif]--><!--[if !vml]-->http://i.msdn.microsoft.com/Global/Images/clear.gif<!--[endif]-->复制代码

DynamicMethod dm = new DynamicMethod("tempMethod" +

Guid.NewGuid().ToString(), null, null, this.GetType());

ILGenerator il = dm.GetILGenerator();

il.Emit(OpCodes.Ldstr, "Hello, World!");

MethodInfo cw = typeof(Console).GetMethod("WriteLine",

new Type[] { typeof(string) });

il.Emit(OpCodes.Call, cw);

dm.Invoke(null, null);

动态方法的主要优势是 MSLI 和所有相关代码生成数据结构均被分配在托管堆上。这意味着一旦 DynamicMethod 的最后一个引用超出范围,垃圾收集器就能够回收内存。

XmlSerializer 泄漏:.NET Framework 中的某些部分(例如 XmlSerializer)会在内部使用动态代码生成。请看下列典型的 XmlSerializer 代码:

<!--[if gte vml 1]><v:shape id="图片_x0020_39" o:spid="_x0000_i1034" type="#_x0000_t75" alt="http://i.msdn.microsoft.com/Global/Images/clear.gif" href="javascript:CopyCode('ctl00_mainContentContainer_ctl13');" title="&quot;复制代码&quot;" style='width:6.75pt;height:6.75pt;visibility:visible;mso-wrap-style:square' o:button="t"> <v:imagedata src="file:///C:/DOCUME~1/DENGSH~1/LOCALS~1/Temp/msohtmlclip1/01/clip_image001.gif" o:title="clear" /> </v:shape><![endif]--><!--[if !vml]-->http://i.msdn.microsoft.com/Global/Images/clear.gif<!--[endif]-->复制代码

XmlSerializer serializer = new XmlSerializer(typeof(Person));

serializer.Serialize(outputStream, person);

XmlSerializer 构造函数将使用反射来分析 Person 类,并藉此生成一对由 XmlSerializationReader XmlSerializationWriter 派生而来的类。它将创建临时的 C# 文件,将结果文件编译成临时程序集,并最终将该程序集加载到进程。通过这种方式生成的代码同样需要相当大的开销。因此 XmlSerializer 对每种类型的临时程序集进行缓存。也就是说,下一次为 Person 类创建 XmlSerializer 时,会使用缓存的程序集,而不再生成新的程序集。

默认情况下,XmlSerializer 所使用的 XmlElement 名称就是该类的名称。因此,Person 将被序列化为:

<!--[if gte vml 1]><v:shape id="图片_x0020_40" o:spid="_x0000_i1033" type="#_x0000_t75" alt="http://i.msdn.microsoft.com/Global/Images/clear.gif" href="javascript:CopyCode('ctl00_mainContentContainer_ctl14');" title="&quot;复制代码&quot;" style='width:6.75pt;height:6.75pt;visibility:visible;mso-wrap-style:square' o:button="t"> <v:imagedata src="file:///C:/DOCUME~1/DENGSH~1/LOCALS~1/Temp/msohtmlclip1/01/clip_image001.gif" o:title="clear" /> </v:shape><![endif]--><!--[if !vml]-->http://i.msdn.microsoft.com/Global/Images/clear.gif<!--[endif]-->复制代码

<?xml Version="1.0" encoding="utf-8"?>

<Person xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:xsd="http://www.w3.org/2001/XMLSchema">

<Id>5d49c002-089d-4445-ac4a-acb8519e62c9</Id>

<FirstName>John</FirstName>

<LastName>Doe</LastName>

</Person>

有 时有必要在不改变类名称的前提下改变根元素的名称。(要与现有架构兼容可能需要根元素名称。)因此 Person 可能需要被序列化为 <PersonInstance>XmlSerializer 构造函数能够很方便地被重载,将根元素名称作为第二参数,如下所示:

<!--[if gte vml 1]><v:shape id="图片_x0020_41" o:spid="_x0000_i1032" type="#_x0000_t75" alt="http://i.msdn.microsoft.com/Global/Images/clear.gif" href="javascript:CopyCode('ctl00_mainContentContainer_ctl15');" title="&quot;复制代码&quot;" style='width:6.75pt;height:6.75pt;visibility:visible;mso-wrap-style:square' o:button="t"> <v:imagedata src="file:///C:/DOCUME~1/DENGSH~1/LOCALS~1/Temp/msohtmlclip1/01/clip_image001.gif" o:title="clear" /> </v:shape><![endif]--><!--[if !vml]-->http://i.msdn.microsoft.com/Global/Images/clear.gif<!--[endif]-->复制代码

XmlSerializer serializer = new XmlSerializer(typeof(Person),

new XmlRootAttribute("PersonInstance"));

当 应用程序开始对 Person 对象进行序列化/反序列化时,一切运转正常,直至引发 OutOfMemoryException。对 XmlSerializer 构造函数的重载并不会对动态生成的程序集进行缓存,而是在每次实例化新的 XmlSerializer 时生成新的临时程序集。这时应用程序以临时程序集的形式泄漏非托管内存。

要修复该泄漏,请在类中使用 XmlRootAttribute 以更改序列化类型的根元素名称:

<!--[if gte vml 1]><v:shape id="图片_x0020_42" o:spid="_x0000_i1031" type="#_x0000_t75" alt="http://i.msdn.microsoft.com/Global/Images/clear.gif" href="javascript:CopyCode('ctl00_mainContentContainer_ctl16');" title="&quot;复制代码&quot;" style='width:6.75pt;height:6.75pt;visibility:visible;mso-wrap-style:square' o:button="t"> <v:imagedata src="file:///C:/DOCUME~1/DENGSH~1/LOCALS~1/Temp/msohtmlclip1/01/clip_image001.gif" o:title="clear" /> </v:shape><![endif]--><!--[if !vml]-->http://i.msdn.microsoft.com/Global/Images/clear.gif<!--[endif]-->复制代码

[XmlRoot("PersonInstance")]

public class Person {

// code

}

如果直接将属性赋予类型,则 XmlSerializer 对为类型所生成的程序集进行缓存,从而避免了内存的泄漏。如果需要对根元素名称进行动态切换,应用程序能够利用工厂对其进行检索,从而对 XmlSerializer 实例自身进行缓存。

<!--[if gte vml 1]><v:shape id="图片_x0020_43" o:spid="_x0000_i1030" type="#_x0000_t75" alt="http://i.msdn.microsoft.com/Global/Images/clear.gif" href="javascript:CopyCode('ctl00_mainContentContainer_ctl17');" title="&quot;复制代码&quot;" style='width:6.75pt;height:6.75pt;visibility:visible;mso-wrap-style:square' o:button="t"> <v:imagedata src="file:///C:/DOCUME~1/DENGSH~1/LOCALS~1/Temp/msohtmlclip1/01/clip_image001.gif" o:title="clear" /> </v:shape><![endif]--><!--[if !vml]-->http://i.msdn.microsoft.com/Global/Images/clear.gif<!--[endif]-->复制代码

XmlSerializer serializer = XmlSerializerFactory.Create(

typeof(Person), "PersonInstance");

XmlSerializerFactory 是我创建的一个类,它可以使用 PersonInstance 根元素名称来检查 Dictionary<Tkey, Tvalue> 中是否包含有用于 Person Xmlserializer。如果包含,则返回该实例。如果不包含,则创建一个新的实例,并将其存储在哈希表中返回给调用方。

泄漏托管堆内存

现 在让我们关注一下托管内存的泄漏。在处理托管内存时,垃圾收集器会帮助我们完成绝大部分的工作。我们需要向垃圾收集器提供工作所需的信息。但是,在很 多场合下,垃圾收集器无法有效地工作,导致需要使用比正常工作要求更高的托管内存。这些情况包括大型对象堆碎片、不必要的根引用以及中年危机。

大型对象堆碎片 如果一个对象的大小为 85,000 字节或者更大,就要被分配在大型对象堆上。请注意,这里是指对象自身的大小,并非任何子对象的大小。以下列类为例:

<!--[if gte vml 1]><v:shape id="图片_x0020_44" o:spid="_x0000_i1029" type="#_x0000_t75" alt="http://i.msdn.microsoft.com/Global/Images/clear.gif" href="javascript:CopyCode('ctl00_mainContentContainer_ctl18');" title="&quot;复制代码&quot;" style='width:6.75pt;height:6.75pt;visibility:visible;mso-wrap-style:square' o:button="t"> <v:imagedata src="file:///C:/DOCUME~1/DENGSH~1/LOCALS~1/Temp/msohtmlclip1/01/clip_image001.gif" o:title="clear" /> </v:shape><![endif]--><!--[if !vml]-->http://i.msdn.microsoft.com/Global/Images/clear.gif<!--[endif]-->复制代码

public class Foo {

private byte[] m_buffer = new byte[90000]; // large object heap

}

由于 Foo 实例仅含有一个 4 字节(32 位框架)或 8 字节(64 位框架)的缓冲区引用,以及一些 .NET Framework 使用的内务数据,因此将被分配在普通的分代式托管堆上。缓冲区将分配在大型对象堆上。

与 其他的托管堆不同,由于移动大型对象耗费资源,所以大型对象堆不会被压缩。因此,当大型对象被分配、释放并清理后,就会出现空隙。根据使用模式的不同,大 型对象堆中的这些空隙可能会使内存使用率明显高于当前分配的大型对象所需的内存使用率。本月下载中包含的 LOHFragmentation 应用程序会在大型对象堆中随机分配和释放字节数组,从而用实例证实了这一点。应用程序运行几次后,能通过释放字节数组的方式创建出恰好与空隙相符的新的字 节数组。在应用程序的另外几次运行中,则未出现这种情况,内存需要量远远大于当前分配的字节数组的内存需要量。您可以使用诸如 CLRProfiler 的内存分析器来将大型对象堆的碎片可视化。 3 中的红色区域为已分配的字节数组,而白色区域则代表未分配的空间。

<!--[if gte vml 1]><v:shape id="图片_x0020_45" o:spid="_x0000_i1028" type="#_x0000_t75" alt="http://i.msdn.microsoft.com/cc163491.fig03%28zh-cn%29.gif" style='width:300pt;height:144.75pt;visibility:visible;mso-wrap-style:square'> <v:imagedata src="file:///C:/DOCUME~1/DENGSH~1/LOCALS~1/Temp/msohtmlclip1/01/clip_image003.gif" o:title="cc163491.fig03%28zh-cn%29" /> </v:shape><![endif]--><!--[if !vml]-->http://i.msdn.microsoft.com/cc163491.fig03%28zh-cn%29.gif<!--[endif]-->

3CLRProfiler 中的大型对象堆(单击该图像获得较大视图)

目 前尚无一种单一的解决方案能够避免大型对象堆碎片的产生。您可以使用类似 CLRProfiler 的工具对应用程序的内存使用情况,特别是大型对象堆中的对象类型进行检查。如果碎片是由于重新分配缓冲区而产生的,则请保持固定数量的重用缓冲区。如果碎 片是由于大量字符串串连而产生的,请检查 System.Text.StringBuilder 类是否能够减少创建临时字符串的数量。基本策略是要确定如何降低应用程序对临时大型对象的依赖,而临时大型对象正是大型对象堆中产生空隙的原因所在。

不必要的根引用 让我们思考一下垃圾收集器是如何决定回收内存的时间。当 CLR 试 图分配内存并保留不足的内存时,它就在扮演着垃圾收集器的角色。垃圾收集器列出了所有的根引用,包括位于任何线程的调用堆栈上的静态字段和域内局部变 量。垃圾收集器将这些引用标记为可访问,并跟据这些对象所包含的引用,将其同样标记为可访问。这一过程将持续进行,直至所有可访问的引用均被访问。任何没 有被标记的对象都是无法访问的,因此是垃圾。垃圾收集器对托管堆进行压缩,整理引用以指向它们在堆中的新位置,并将控件返回给 CLR。如果释放充足的内存,则使用此释放的内存进行分配。如果释放的内存不足,则向操作系统请求额外的内存。

如 果我们忘记清空根引用,系统会立即阻止垃圾收集器有效地释放内存,从而导致应用程序需要更多的内存。问题可能微妙,例如一种方法,它能够在做出与查询数据 库或调用某个 Web 服务相类似的远程调用前为临时对象创建大型图形。如果垃圾收集发生在远程调用期间,则整个图形被标记为可访问的,并不会收集。这样会导致更大的开销,因为 在收集中得以保留的对象将被提升到下一代,这将引起所谓的中年危机。

中年危机 中年危机不会使应用程序去购买一辆保时捷。但它却可以造成托管堆内存的过度使用,并使垃圾收集器花费过多的处理器时间。正如前面所提到的,垃圾收集器使用 分代式算法,采取试探性的推断,它会认为如果对象已经存活一段时期,则有可能存活更长的一段时期。例如,在 Windows 窗体应用程序中,应用程序启动时会创建主窗体,主窗体关闭时应用程序则退出。对于垃圾收集器来说,持续地验证主窗体是否正在被引用是一件浪费资源的事。当 系统需要内存以满足分配请求时,会首先执行第 0 代收集。如果没有足够的可用内存,则执行第 1 代收集。如果仍然无法满足分配请求,则继续执行第 2 代收集,这将导致整个托管堆以极大的开销进行清理工作。第 0 代收集的开销相对较低,因为只有当前被分配的对象才被认为是需要收集的。

如果对象有继续存活至第 1 代(或更严重至第 2 代)的趋势,但却随即死亡,此时就会出现中年危机。这样做的效果是使得开销低的第 0 代收集转变为开销大得多的第 1 代(或第 2 代)收集。为什么会发生这种现象呢?请看下面的代码:

<!--[if gte vml 1]><v:shape id="图片_x0020_46" o:spid="_x0000_i1027" type="#_x0000_t75" alt="http://i.msdn.microsoft.com/Global/Images/clear.gif" href="javascript:CopyCode('ctl00_mainContentContainer_ctl20');" title="&quot;复制代码&quot;" style='width:6.75pt;height:6.75pt;visibility:visible;mso-wrap-style:square' o:button="t"> <v:imagedata src="file:///C:/DOCUME~1/DENGSH~1/LOCALS~1/Temp/msohtmlclip1/01/clip_image001.gif" o:title="clear" /> </v:shape><![endif]--><!--[if !vml]-->http://i.msdn.microsoft.com/Global/Images/clear.gif<!--[endif]-->复制代码

class Foo {

~Foo() { }

}

对 象将始终在第 1 代收集中被回收!终结器 ~Foo() 使我们可以实现对象的代码清理,除非强行终止 AppDomain,否则代码将在对象内存被释放前运行。垃圾收集器的任务是尽快地释放尽可能多的托管内存。终结器是由用户编写的代码,并且毫无疑问可以 执行任何操作。虽然我们并不建议,但是终结器也会执行一些愚蠢的操作,例如将日志记录到数据库或调用 Thread.Sleep(int.MaxValue)。因此,当垃圾收集器发现具有终结器但未被引用的对象时,会将该对象加入到终结队列中,并继续工 作。该对象由此在垃圾收集中得以保留,被提升一代。这里甚至为其准备了一个性能计数器:.NET CLR Memory-Finalization Survivors,可显示最后一次垃圾收集期间由于具有终结器而得以保留的对象的数量。最后,终结器线程将运行对象的终结器,随后对象即被收集。但此时 您已经从开销低的第 0 代收集转变为第 1 代收集,而您仅仅是添加了一个终结器!

大 多数情况下,编写托管代码时终结器并不是必不可少的。只有当托管对象具有需要清理的非托管资源的引用时,才需要终结器。而且即使这样,您也应该使用 SafeHandle 派生类型来对非托管资源进行包装,而不要使用终结器。此外,如果您使用非托管资源或其他实现 Idispoable 的托管类型,请实现 Dispose 模式来让使用对象的用户大胆地清理资源,并避免使用任何相关的终结器。

如果一个对象仅拥有其他托管对象的引用,垃圾收集器将对未引用的对象进行清理。这一点与 C++ 截然不同,在 C++ 中必须在子对象上调用删除命令。如果终结器为空或仅仅将子对象引用清空,请将其删除。将对象不必要地提升至更高一代将对性能造成影响,使清理开销更高。

还有一些做法会导致中年危机,例如在进行查询数据库、在另一线程上阻塞或调用 Web 服务等阻塞调用之前保持对对象的持有。在调用过程中,可以发生一次或多次收集,并由此使得开销低的第 0 代对象提升至更高一代,从而再次导致更高的内存使用率和收集成本。

还 有一种情况,它与事件处理程序和回调一起发生并且更难理解。我将以 ASP.NET 为例,但同样类型的问题也会发生在任何应用程序中。考虑一下执行一次开销很大的查询,然后等上 5 分钟才可以缓存查询结果的情况。查询是属于页面查询,并基于查询字符串参数来进行。当一项内容从缓存中删除时,事件处理程序将进行记录,以监视缓存行 为。(参见 4)。

<!--[if gte vml 1]><v:shape id="ctl00_mainContentContainer_cpe141984_i" o:spid="_x0000_i1026" type="#_x0000_t75" alt="http://i.msdn.microsoft.com/Global/Images/clear.gif" style='width:.75pt;height:.75pt;visibility:visible;mso-wrap-style:square'> <v:imagedata src="file:///C:/DOCUME~1/DENGSH~1/LOCALS~1/Temp/msohtmlclip1/01/clip_image001.gif" o:title="clear" /> </v:shape><![endif]--><!--[if !vml]-->http://i.msdn.microsoft.com/Global/Images/clear.gif<!--[endif]-->Figure4记录从缓存中移除的项

<!--[if gte vml 1]><v:shape id="图片_x0020_48" o:spid="_x0000_i1025" type="#_x0000_t75" alt="http://i.msdn.microsoft.com/Global/Images/clear.gif" href="javascript:CopyCode('ctl00_mainContentContainer_ctl24');" title="&quot;复制代码&quot;" style='width:6.75pt;height:6.75pt;visibility:visible;mso-wrap-style:square' o:button="t"> <v:imagedata src="file:///C:/DOCUME~1/DENGSH~1/LOCALS~1/Temp/msohtmlclip1/01/clip_image001.gif" o:title="clear" /> </v:shape><![endif]--><!--[if !vml]-->http://i.msdn.microsoft.com/Global/Images/clear.gif<!--[endif]-->复制代码

protected void Page_Load(object sender, EventArgs e) {

string cacheKey = buildCacheKey(Request.Url, Request.QueryString);

object cachedObject = Cache.Get(cacheKey);

if(cachedObject == null) {

cachedObject = someExpensiveQuery();

Cache.Add(cacheKey, cachedObject, null,

Cache.NoAbsoluteExpiration,

TimeSpan.FromMinutes(5), CacheItemPriority.Default,

new CacheItemRemovedCallback(OnCacheItemRemoved));

}

... // Continue with normal page processing

}

private void OnCacheItemRemoved(string key, object value,

CacheItemRemovedReason reason) {

... // Do some logging here

}

看上去正常的代码实际上隐含着严重的错误。所有这些 ASP.NET Page 实例都变成了永世长存的对象。OnCacheItemRemoved 是一个实例方法,CacheItemRemovedCallback 委托中包含了一个隐式的“this”指针,这里的“this”即 为 Page 实例。该委托被添加至 Cache 对象。这样,就会产生一个从 Cache 到委托再到 Page 实例的依赖关系。在进行垃圾收集时,可以一直从根引用(Cache 对象)访问 Page 实例。这时,Page 实例(以及在呈现时它所创建的所有临时对象)至少需要等待五分钟才能被收集,在此期间,它们都有可能被提升至第 2 代。幸运地是,有一种简单的方法能够解决该示例中的问题。请将回调函数变为静态。Page 实例上的依赖关系就会被打破,从而可以像第 0 代对象一样以很低的开销来进行收集。

总结

我 已经就 .NET 应用程序中能够导致内存泄漏或内存消耗过度的各种问题进行了讨论。虽然 .NET 可减少您对内存方面的关注程度,但是您仍必须关注应用程序的内存使用情况,以确保应用程序高效正常运行。虽然应用程序被托管,但这并不意味着您可以依靠垃 圾收集器就能解决所有问题而将良好的软件工程实践束之高阁。虽然在应用程序的开发和测试阶段,您必须对其内存性能进行持续不断的监视。但是这样做非常值 得。要记住,只有让用户满意才称得上是功能良好的应用程序。

James Kovacs居 住在加拿大阿尔伯达省卡尔加里市,同时身为独立架构师、开发人员和培训师,对各个领域都很精通,尤其擅长 .NET Framework、安全和企业应用程序开发。James Kovacs Microsoft 的解决方案架构 MVP,拥有哈佛大学硕士学位。James 的联系方式如下:jkovacs@post.harvard.edu www.jameskovacs.com

分享到:
评论

相关推荐

    VC使用CRT调试功能来检测内存泄漏.rar_VC 内存_VC 调试_Vc_内存泄漏

    VC使用CRT调试功能来检测内存泄漏 C/C++ 编程语言的最强大功能之一便是其动态分配和释放内存,但是中国有句古话:“最大的长处也可能成为最大的弱点”,那么 C/C++ 应用程序正好印证了这句话。在 C/C++ 应用程序...

    node-memory-leak-tutorial, 在节点中,调试内存泄漏的教程.zip

    node-memory-leak-tutorial, 在节点中,调试内存泄漏的教程 调试 node.js 中的内存泄漏这是在 node.js. 中调试内存泄漏的快速教程步骤 1: 安装调试工具首先,你需要安装 v8-profiler 模块。 如果不需要在应用程序中...

    MemoryAnalyzer分析内存泄露工具

    MemoryAnalyzer软件可以帮你进行发现或者分析调试的代码等无法进行修复的问题;支持分析内存问题.;支持监控整个 Java 应用程序的状态与行为.还可以进行读取,使应用程序运行时可以支持让Java 运行时环境的生产

    WinDbg 调试程序崩溃操作详解

    作者有10多年编程,调试经验,详解关于Windows 平台下程序崩溃的Windbg调试方法及技巧;附带工具介绍 适合于分析C++等语言, .Net 平台等快速入门及提高

    新版MQL语言编程-检测内存泄露-快速找到内存泄露的行号和文件名

    1.由于MT4和MT5在运行应用程序(EA、指标、脚本)时,是不会报告内存泄漏的具体位置的。 2.如果在MQL代码中, 使用 new运算符,而忘记调用delete运算符来释放占用的内存,则会发生内存泄漏。 3.当开发复杂的程序时,去...

    vc内存泄露检测

    C/C++ 编程语言的最强大功能之一便是其动态... 幸运的是,Visual Studio 调试器和 C 运行时 (CRT) 库为我们提供了检测和识别内存泄漏的有效方法。下面请和我一起分享收获——如何使用 CRT 调试功能来检测内存泄漏?

    leaky-example:内存泄漏的示例应用程序

    泄漏示例应用这是一个简单的泄漏Express应用程序的示例,可在有关调试,监视和分析Node应用程序的会议讨论中使用。 随意克隆/分叉/扩展/演示/使用/随您喜欢的这个应用程序。运行它 ~$ git clone ...

    aether:多合一内存泄漏测试解决方案

    显然缺乏用于在Node.JS / Javascript应用程序中调试和分析内存使用情况的工具,因此仍然需要一种工具来以图表格式实时更新数据。 将aether_memory npm软件包安装到服务器后,您可以通过查看图表报告来观察内存使用...

    MTuner:MTuner是CC ++内存分析器和内存泄漏查找器,适用于Windows,PlayStation 4和3,Android和其他平台

    创建MTuner时是为了对C / C ++应用程序进行性能分析,只要存在相应的调试符号信息,就可以为使用其他语言编写的程序配置内存。 用户可以使用将DMD CodeView / DWARF调试符号转换为PDB格式。 源代码 您可以通过从...

    Android App调试内存泄露之Cursor篇

    最近在工作中处理了一些内存泄露的问题,在这个过程中我尤其发现了一些基本的问题反而忽略导致内存泄露,比如静态变量,cursor关闭,线程,定时器,反注册,bitmap等等,我稍微统计并总结了一下,当然了,这些问题...

    MemProof(内存清道夫)

    MemProof(内存清道夫)是AutomatedQA出品的一款非常不错的检测内存泄漏和资源泄漏的免费调试工具,适合于WIN32平台下使用DELPHI/C++ BUILDER开发的应用程序。利用它可以方便的查找出一些忘记释放的指针以及资源。它...

    应用调试技术(英)

    介绍程序的调试技术的经典书籍,对于发布程序的调试具有指导意义。常用于内存泄露的寻找。

    Advanced Windows Debugging(windows高级调试英文版)

    工具简介、调试器简介、调试器揭密、符号文件与源文件的管理、栈内存破坏、堆内存破坏、安全、进程间通信、资源泄漏、同步、编写定制的调试扩展、64位调试、事后调试、WindowsVista基础以及应用程序验证器的测试设置...

    内存管理内存管理内存管理

    程序的动态性越强,内存管理就越重要,您的内存分配程序的选择也就更重要。让我们来了解可用于内存管理的不同方法,它们的好处与不足,以及它们最适用的情形。 C 风格的内存分配程序 C 编程语言提供了两个函数来...

    Java Web开发常见问题.docx

    404/500错误:当部署Web应用程序时,经常会出现404和500...日志问题:在Java Web应用程序中,日志是重要的调试和故障排除工具。但是,如果日志记录级别设置过高或日志文件过大,可能会导致性能问题或存储空间问题。

    UnityHeapExplorer:适用于Unity 2019.3及更高版本的内存分析器,调试器和分析器

    我过去花费了大量时间来确定和修复内存泄漏,并在过去寻找Unity应用程序中的内存优化机会。 在这段时间里,我经常使用Unity ,虽然它是一个有用的工具,但我从未完全满意。 这使我编写了自己的内存事件探查器,在...

    操作系统(内存管理)

    free:该函数获得指向由 malloc 分配的内存片段的指针,并将其释放,以便以后的程序或操作系统使用(实际上,一些 malloc 实现只能将内存归还给程序,而无法将内存归还给操作系统)。 物理内存和虚拟内存 要理解...

    IBMRationalPurify的高级特性:利用Purify进行程序调试

    本文内容包括:应用程序编程接口利用带有调试器的RationalPurify内存观察点利用您程序中的PurifyAPI总结参考资料IBM:registered:Rational:registered:Purify:registered:是一个能够精确检测内存泄露错误的工具,否则...

Global site tag (gtag.js) - Google Analytics