为什么写这个dotmemory呢,还是因为之前面试,被血虐。虽然之前看过两遍CLR VIA C#,但是里面讲的CLR知识都是理论说的,自己并没有实际去验证。面试的时候,面试官问了一些内存堆栈和寄存器缓存,内存分配和优化以及gc的一些比较细的问题。被锤了。于是就想通过一些工具看看到底怎么回事,验证一下Clr的内存等等相关的知识。可是十一很多的梯子全被墙了,google用不了,结果百度出来的,根本没这方面知识,唯一有的就只有一篇教程,结果这个教程写的感觉很不走心,对比java的相关方面的内容,随便一查全是,而且写的很细致各种书,真的.net生态差太远。于是有些生气,但是又没办法,毕竟生态还是得靠我们这些搞.net的慢慢努力,于是就准备将官方文档结合我的应城英语翻译一个教程。而且想了下,同事们,自己也写了很多厉害的工具,估计这个dotmemory写完会写一下windbg和同事们自己写的一些工具。

       在本教程中,我们将学习如何使用dotMemory 以及如何获取内存快照。此外,我们将简要介绍dotMemory的用户界面和基本概念。并将以此教程作为您dotMemory的入门教程。

        基本术语 #

      你可能会问:“什么是内存快照,它们有什么作用?”所谓内存快照,就是就是程序运行时,某一个时刻的内存使用和分配情况,类似于把这个时刻的内存相关信息拍了个照。快照的理解都差不多,大家可以类比redis快照来理解。当您使用了DotMemory后,您就会对这些有一定的印象了。

        从内存的角度来看,应用程序的工作包括不断地对新对象分配内存,以及释放应用程序不再使用的对象留下的内存。对象在所谓的托管堆中被一个接一个地分配。基于此,作为一个内存分析器我们有两个基本的操作必须能够做到:

        1)获取内存快照。快照是托管堆的即时映像。每个快照都包含了在单击快照按钮时,应用程序在内存中分配的所有对象的信息。

        2)收集内存交通信息。内存交通显示您分配和释放了多少内存,例如,在两个内存快照之间。这些信息也非常有价值,因为它可以让您了解应用程序内存是如何动态变化的。

       当然,在学习本教程时,您还将了解一些其他术语。但现在这已经足够理解接下来几步的教程了。让我们开始吧!

      示例应用程序 #

    首先,我们需要一个用于分析的应用程序。贯穿这一系列完整的dotMemory教程,我们将使用这同一个C#语言应用程序,作为示例。它模仿了经典的康威生命游戏,你可能知道。如果没有,请查阅维基百科。这不会花费很多时间,但会使教程的理解变得容易得多。所以,在我们开始之前,请从github下载应用程序。下载地址://TODO:初始版本康威生命游戏

        Step 1. 运行DotMemory

       1.至于dotmemory的下载安装,等等本文不再赘述,因为网上已经很多了。打开dotmemory主窗口,现在让我们开始一个分析会话吧。

        

     2.在左侧面板上选择“Local”,然后在“Profile Application”中,选择“Standalone”。

     3.现在我们应该在右侧面板中配置分析会话选项。

      1)在右侧面板中,指定您刚下载的康威生命游戏可执行文件的路径

      2)选择“Collect memory allocation and traffic data from start”选项,这个作用是告诉dotmemory在启动应用程序后立即开始收集分配调用堆栈数据。

  4.单击"Run"按钮以启动会话分析。这将运行我们的应用程序,并在dotmemory中打开一个新的分析标签。

    Step 2. 获取快照

    一旦应用程序运行,我们就可以获得内存快照。在这个操作中最重要的事情是选择合适的时机获取想要的快照。您还记得,快照是应用程序托管堆的即时映像。因此,在拍摄快照之前,首先要做的是使应用程序处于感兴趣的位置。例如,如果我们想查看在生命游戏启动后创建的对象,我们必须在应用程序中执行任何操作之前获取快照。相反,如果我们需要知道哪些对象是动态创建的,则必须在单击应用程序中的“Start”后拍摄快照。

  1.假设我们需要获取生命游戏运行时分配的对象的信息。因此,单击应用程序中的“Start”按钮,让游戏运行一段时间。

  2.点击dotmemory中的“Get Snapshot ”按钮。这将捕获快照数据并将快照添加到快照区域。获取快照不会中断这个分析监视的进程,因此允许我们获取其他的快照(可以继续点击 “Get Snapshot”获取其它快照)。

      

       3.关闭康威生命游戏窗口,结束我们刚才的内存分析监视会话。

       4.看看DotMemory。现在,主页面包含了包含基本信息的单个快照。143.90 MBtotal 意味着应用程序总共消耗143.90 MB的内存。这个大小等于Windows任务管理器的" Commit size":进程请求的内存量。主要包括:

  1)非托管内存:分配在托管堆之外而不被垃圾收集器管理的内存。一般来说,这是.NETCLR、动态库、图形缓冲区(特别是对使用图形的WPF应用程序特别大)所需的内存,等等。无法在分析器中分析这部分内存。

  2).NET total:托管堆中的内存总量,包括空闲内存(要求了但不被应用程序使用的)。

       3).NET used:应用程序使用了的托管堆中的内存,这是唯一一部分内存,.net能允许你用的.因此,这也是唯一一部分你能在dotmemory这个内存分析器中你能分析的内存。

       请单击快照1链接。让我们更详细地看一下快照。

    Step 3. 认识快照概览 #

     打开快照后,首先看到的是“Inspections”视图。此页显示主要快照热点。

  您能在这里看到的:

  1)Largest Size: 该图显示了消耗内存的主要部分的对象类型。

  2)Largest Retained Size::这个图表示的意思可以参考博客https://www.jianshu.com/p/aaddf00a1d83,对于gc和可达不可达对象写的很详细。

  3)String duplicates, Sparse arrays, Event handlers leak:dotmemory通过一些角度,自动的检查快照的大多数通用类型的内存,如果你不知道怎么开始,这些自动检查的结果图就是一个好的入口点。

  4)Heap Fragmentation:该图显示托管堆段的碎片:Generation 1, 2和大对象堆。.net的GC机制会进行Generation(分代),可以参考博客https://blog.csdn.net/zhang_hui_cs/article/details/86653202,具体可以自己学习,本篇博文毕竟主要说dotmemory

  让我们继续研究快照并查看它包含的对象:

  1.单击“Type”按钮。这将按类型对快照中的所有对象进行分组,并显示“按类型分组”视图。

     学完了基础概念,现在我们可以熟悉dotmemory用户界面和整个内存分析“全家桶”了。

 Step 4. 内存分析入门 #

       在我们进一步深入之前,让我们先讨论下对象是如何存储在内存中的。这是有必要的,为我们更好地理解dotmemory所展示给你的东西。

  内存中对象 #

    应用程序消耗的内存的大部分被分配给应用程序的对象。对象存储数据并引用其他对象。对象及其引用构成对象图。例如,照片类的对象将存储长值类型的ID字段,以及其他的一些字段(如List<PhotoComment> _comments)。

    class Photo
    {
        long _id;
        String _title;
        User _user;
        List<PhotoComment> _comments;
    }

    

 应用程序根 #

 当应用程序需要内存时,.NET的垃圾收集器(GC)决定并删除不再需要的对象。要做到这一点,GC将从根开始, 即静态字段、局部变量和外部句柄,开始对每个对象进行链式的GC搜索。如果不可达对象,则被认为不再需要,并且从内存中删除。在下面的示例中,对象D和F将从内存中移除,因为它们不能从应用程序的根访问到。

  Retention #

  这里我们来谈谈留用的关键概念。 

  从根到一个对象的路径可能会穿过许多其他对象。如果对象B的所有路径都通过对象A,那么A是B的支配者。换句话说,B只在A.内存中留用,如果A是被GC的,B也将被GC。这就是为什么每个对象最重要的参数是它留用的对象的大小。在dotMemory中,这个参数称为"Retained bytes"。例如,下面示例中的对象c保留632字节。对象B并非由C独占留用;因此,它不包括在计算中。

 

 让我们返回到dotmemory,看一看刚才打开的“按类型分组”的视图。此视图当前显示堆中的所有对象,由它们独占留用的内存大小排序。正如你所看到的,主要部分由“System.Windows.Shapes.Ellipse”留用(很明显,这些是我们用来可视化生命细胞游戏的椭圆形状)。该类型的对象留用11871980字节的内存,同时消耗3862600字节。

  一旦你熟悉了主要的分析术语,让我们来看看我们如何使用DotMemory。

  熟悉用户界面 #

      我们想让您把使用DotMemory想象成一种调查,比如某种犯罪调查(用dotMemory进行内存分析)。这里的主要思想是收集数据(一个或多个内存快照),并选择一些嫌疑人(可能导致问题的分析对象)。所以,你从一些嫌疑犯名单开始,逐渐缩小这个名单。一个嫌疑犯可能会把你引向另一个嫌疑犯,以此类推,直到你确定真正的罪犯为止。

  1.请看DotMemory的左侧窗口。这是一条分析路径,显示了你所有的调查步骤。

           

  分析路径中的每个项目都是您分析的主题。正如您所看到的,您从分析GAMOFLIFE .EXE(步骤步骤1)开始,然后打开快照(1)(步骤2),并要求dotmemory显示堆中的所有对象(对象集所有对象)。即使是一个很小的应用程序也会创建许多对象,因此尝试分别分析每个对象将不会非常搞笑。这就是为什么你在使用dotmemory中分析的主要主题是通过所谓的对象集。

  对象集是由特定条件选择的多个对象。为了便于理解,可以将对象集看作某个查询的结果(非常类似于sql查询)。例如,您可以告诉dotMemory,比如“选择SomeCall()创建的所有对象,或者“选择由实例A留存在内存中的所有对象”等等。

  2.每个对象集可以从不同的视角被视作视图。看看屏幕。您看到的视图称为“按类型分组”,它显示按类型分组的集合中对象的简单列表。其他视图可以显示有关选定集的其他信息。您可以使用屏幕顶部的按钮轻松更改视图:

  如上所述,你分析的每一个主题可能会引导你进入另一个主题。例如,我们看到System.Windows.Shapes.Ellipse类保留了大部分内存,我们想知道创建这些椭圆的方法是什么。让我们看看它。

       3.双击System.Windows.Shapes.Ellipse或打开这些对象的上下文菜单(单击鼠标右键),然后选择“Open this object set”。

      4.选中“Back Traces”视图

  该视图表明,我们的椭圆起源于Grid.InitCellsVisuals() 这个方法。请注意,分析路径现在还包含一个步骤:按system.windows.shapes.elliple类型分组。

       

01-25 13:04