zoukankan      html  css  js  c++  java
  • dump文件解析之探索.Net的内存

    前言:

    对于需要长时间运行的.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程序

    然后运行

     

     

    一.用Visual Studio

    打开dump文件

     

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

     

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

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

    1.根的路径

     

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

     

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

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

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

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

    2.引用的类型

     

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

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

     

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

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

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

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

    二.DebugDiag

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

     

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

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

     

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

    三.WinDbg

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

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

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

    注意要设置下Symbol Path

     

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

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

     

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

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

     

    如下图所示:

     

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

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

     【 !loadby sos clr】 命令

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

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

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

    使用命令 【!DumpHeap -stat】

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

     

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

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

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

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

     

    结果如下:

     

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

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

    使用命令【!GCRoot 】

     

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

    WinDbg的其他常用命令

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

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

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

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

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

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

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

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

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

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

     下图是死锁情况

     

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

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

     

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

    使用SOSEX更方便的使用WinDbg

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

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

     

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

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

     

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

     

    总结

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

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

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

    一定会让你有所发现。

  • 相关阅读:
    控件还是还原到客户端的好
    练习之彩票一 需求整理和分析
    Oracle 查询并删除重复记录的SQL语句
    C# 如何生成CHM帮助文件
    C#生成CHM帮助文件—>续
    datagridview中用Enter代替tab实现焦点切换,可换行
    博客园的dotaer
    winfrom中datagridview指定单元格为编辑状态
    C#生成CHM帮助文件(linq版)
    C语言03
  • 原文地址:https://www.cnblogs.com/yudongdong/p/9701727.html
Copyright © 2011-2022 走看看