dump文件解析之探索.Net的内存 二.DebugDiag 三.WinDbg 总结

dump文件解析之探索.Net的内存
二.DebugDiag
三.WinDbg
总结

前言:

对于需要长时间运行的.net程序,有时需要我们查看内存的使用有没有内存泄露问题。

我们可以从dump文件中找到答案。

Dump的看点

用dump文件来分析内存,到底我们需要关心哪些点呢?

  1. 内存的使用情况 HeapSize/object的数量 也就是托管堆使用大小以及托管堆内有多少数量的对象

    1.1  查看有没有存在有占用大量内存的对象 <比如有某类下面的一个集合>

      1.2  0 1 2各代的size<查看各代的内存是否有异常>

       2.调查是否有内存泄露(重点)

    2.1  查看object的根(Root) 看看GC回收不了的有哪些

    2.2<我们知道一个对象Root下没有引用就会标为可Gc对象,如果一个对象你希望被gc回收但写代码不注意又在别的地方保存了引用就会出现内存泄露>

  3.  终结器是否被阻塞时,当终结器线程被阻塞时,Finalize会等待累积(末尾有例子)

用什么工具

  • Visual Studio
  • DebugDiag
  • WinDbg
  • dotMemory(JetBrains旗下的 我还没研究过)

以上三款是微软给我们提供的工具,注意VS得要是Enterprise才可以哦。其他的两款都是免费的。

我们先写一个sample程序

dump文件解析之探索.Net的内存
二.DebugDiag
三.WinDbg
总结

然后运行

 dump文件解析之探索.Net的内存
二.DebugDiag
三.WinDbg
总结

 dump文件解析之探索.Net的内存
二.DebugDiag
三.WinDbg
总结

一.用Visual Studio

打开dump文件

 dump文件解析之探索.Net的内存
二.DebugDiag
三.WinDbg
总结

点击按钮 【调试托管内存】

 dump文件解析之探索.Net的内存
二.DebugDiag
三.WinDbg
总结

可以很清楚的看到有多少对象,每个对象共使用了多少内存

在这个一览下方有2个视图 分别是

1.根的路径

 dump文件解析之探索.Net的内存
二.DebugDiag
三.WinDbg
总结

比如我们选择 ConsoleApp2.B 这个对象

 dump文件解析之探索.Net的内存
二.DebugDiag
三.WinDbg
总结

从这个图可以看出来 B 这个对应 的 Paths To Root的追溯情况 (也就是构建最终要GC的Root)

 Program._values(static变量) -> List<A> -> B

我们可以看到values就是B的Root 只要values不存在那B就会纳入gc的回收对象中

因为我们是在Hold住了这个程序的main方法所以在这个时候B 对象还不能被gc回收

2.引用的类型

 dump文件解析之探索.Net的内存
二.DebugDiag
三.WinDbg
总结

如何我们选择List<ConsoleApp3.A>

那么就会展示List<ConsoleApp3.A>的引用关系如下图所示:

 dump文件解析之探索.Net的内存
二.DebugDiag
三.WinDbg
总结

从这个图我们可以看出来 List<A> 持有 A[] ,A[]持有 A,A持有B

以上根据这2个视图我们可以利用Vs来看出:

 咦?这个对象占用内存怎么这么大 有点可疑

这个对象不是应该被gc吗,怎么没有被gc呢?研究下他的gc root看看

下载地址 https://www.microsoft.com/en-us/download/details.aspx?id=49924

 dump文件解析之探索.Net的内存
二.DebugDiag
三.WinDbg
总结

点击 【Add Data Files】 添加Dump文件后 点击 【Start Analysis】 执行

执行成功后会自动用 IE 打开。

 dump文件解析之探索.Net的内存
二.DebugDiag
三.WinDbg
总结

其实和 VS比起来差不多,直接生成一个报告也是比较方便的!

三.WinDbg

虽然使用上比较麻烦但是winDbg可以帮助我们分析的更加详细

可以从微软官方下载,为了方便百度云下载地址:

链接: https://pan.baidu.com/s/1eblPm4nuN0F-DkY_FzqUvA 提取码: zmtd

注意要设置下Symbol Path

 dump文件解析之探索.Net的内存
二.DebugDiag
三.WinDbg
总结

重新设置符号文件路径如下;

SRV*C:Symbols*http://msdl.microsoft.com/download/symbols

 dump文件解析之探索.Net的内存
二.DebugDiag
三.WinDbg
总结

意思是如果在本地找不到则从微软网站下载

Ok设置完成后用WinDbg载入dump文件

 dump文件解析之探索.Net的内存
二.DebugDiag
三.WinDbg
总结

如下图所示:

 dump文件解析之探索.Net的内存
二.DebugDiag
三.WinDbg
总结

打开成功后我们还不能开始分析必须

要先执行加载SOS和CLR(对于.Net Runtime 4.0)

 【 !loadby sos clr】 命令

接下来我们用WinDbg来调查内存使用情况:

一般我们定位内存泄露问题,我总结的原则是要查到什么对象占用了大量内存,为什么它没有被GC。我们分以下几个步骤

1.统计内存中的对象&查找有异样的对象

使用命令 【!DumpHeap -stat】

可以把堆中的对象类型和size给打印出来

 dump文件解析之探索.Net的内存
二.DebugDiag
三.WinDbg
总结

我们可以看出来 A 和 B 是可疑对象

2.根据类型查找存活对象一览

接下来我们根据查找出A的一览

使用命令【!DumpHeap -live -mt <MT addr>】

 dump文件解析之探索.Net的内存
二.DebugDiag
三.WinDbg
总结

结果如下:

 dump文件解析之探索.Net的内存
二.DebugDiag
三.WinDbg
总结

可以看出内存中A类型的对象有100000个

3.探索从某个对象找出GC ROOT一览

使用命令【!GCRoot 】

 dump文件解析之探索.Net的内存
二.DebugDiag
三.WinDbg
总结

dump文件解析之探索.Net的内存
二.DebugDiag
三.WinDbg
总结

其实可以看出来和Visual Studio的【根的路径】要点差不多吧。

WinDbg的其他常用命令

1. !DumpHeap -stat 查找托管堆按类型分组统计个数以及占用的总内存大小

dump文件解析之探索.Net的内存
二.DebugDiag
三.WinDbg
总结

2.!HeapStat 查找当前堆中各代的内存使用量 以及剩余使用量

dump文件解析之探索.Net的内存
二.DebugDiag
三.WinDbg
总结

3.!DumpHeap -live -mt <MT addr> 从MethodTable中查找存活的对象一览

dump文件解析之探索.Net的内存
二.DebugDiag
三.WinDbg
总结

4.!DumpHeap -dead -mt <MT addr> 从MethodTable中查找要在下次FullGC要回收的一览

dump文件解析之探索.Net的内存
二.DebugDiag
三.WinDbg
总结

5.!DumpMT -md <MT addr> 查看类型信息 (加了-md参数会把这个类型下的方法(MethodDescriptor)都打印出来)

dump文件解析之探索.Net的内存
二.DebugDiag
三.WinDbg
总结

6.!DumpClass <EEClass addr> 指定EEClass的地址

dump文件解析之探索.Net的内存
二.DebugDiag
三.WinDbg
总结

7.!Threads 查看Finalizer有没有导致死锁的例子

dump文件解析之探索.Net的内存
二.DebugDiag
三.WinDbg
总结

   如上图所示, 用!Threads可以找出 Finalizer的线程为13.。接下来用命令 ~13k 查看线程执行栈,

dump文件解析之探索.Net的内存
二.DebugDiag
三.WinDbg
总结

上图是正常的情况没有问题。执行WaitForFinalizerEvent等下一次执行信号

参考 https://github.com/dotnet/coreclr/blob/ca013149100a9ccc69a5df5b80f29fed2b1b0ce8/src/vm/finalizerthread.cpp#L571

 下图是死锁情况

 dump文件解析之探索.Net的内存
二.DebugDiag
三.WinDbg
总结

可以看到有 CallFinalizer 和 FinalizeAllObjects 表示正在处理什么东西

在用命令【~[ID]e!clrstack】查看CLR的执行栈情况

 dump文件解析之探索.Net的内存
二.DebugDiag
三.WinDbg
总结

可以看出是在Finalize里面用了 Thread.Sleep导致的

使用SOSEX更方便的使用WinDbg

sosez是sos的扩展工具集(就是一个dll文件),下载官网地址:http://www.stevestechspot.com/

下载完后要加载到 WinDbg里面去 使用命令 .load 进行加载

 dump文件解析之探索.Net的内存
二.DebugDiag
三.WinDbg
总结

它集成了很多简单使用的指令

例如: !mdt 可以根据 类型 进行筛选

 dump文件解析之探索.Net的内存
二.DebugDiag
三.WinDbg
总结

蓝色处可以直接点击查看,功能很强大

 dump文件解析之探索.Net的内存
二.DebugDiag
三.WinDbg
总结

总结

.Net程序运行期间会遇到很多奇怪的问题,通过分许Dump文件分析内存情况是一个很好的切入口。

不管用什么工具,按照以下几个步骤:

  • 统计内存中的对象
  • 查找有异样的对象 比如这个对象数量多的有点反常,本来期待会被GC回收但是却没有
  •  根据类型查找存活对象一览
  • 探索从某个对象找出GC ROOT一览

一定会让你有所发现。