【VS】使用vs2017自带的诊断工具(Diagnostic Tools)诊断程序的内存问题

本文介绍使用VS2017自带的Diagnostic Tools诊断程序内存问题。.NET程序虽有GC,但代码不当仍会内存泄漏。以静态变量持有应释放对象为例,介绍捕获内存快照、详细对比、查找泄漏点及查看占用内存多的数据类型的方法,还提及两个过滤选项及生产环境分析方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

【VS】使用vs2017自带的诊断工具(Diagnostic Tools)诊断程序的内存问题

原文:【VS】使用vs2017自带的诊断工具(Diagnostic Tools)诊断程序的内存问题

 

前言

一般来说.NET程序员是不用担心内存分配释放问题的,因为有垃圾收集器(GC)会自动帮你处理。但是GC只能收集那些不再使用的内存(根据对象是否被其它活动的对象所引用)来确定。所以如果代码编写不当的话,仍然会出现内存泄漏的问题,常见的情况有:一个静态变量引用了一个应该被释放的对象,事件注册后不解除注册,非托管资源使用后没有手动释放。不断的内存泄漏终会引起内存不足,挂掉你的程序。

对于这种内存泄漏问题,有很多的分析工具可以使用,常见的有CLRProfiler、ANTS Performance Profiler等。不过从vs2013起,vs自带了一个分析工具-Diagnostic Tool,默认debug时会自动打开,如果没有打开的话就按快捷键Ctrl+Alt+F2。

如下图: 
这里写图片描述

开始使用

这里以静态变量持有应释放的对象为例,简单介绍下使用方法。 
这里有个winform程序,主要功能即使点击按钮后,输出名为“jim”的个人信息:

private void button1_Click(object sender, EventArgs e)
{
     var p = PersonManager.Get("jim");
     label1.Text += p.Name + " " + p.Age + "\n";
}

Person和PersonManager类的信息如下:

namespace MemLeak
{
    public class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
        public byte[] BinaryData { get; set; }
    }
    public static class PersonManager
    {
        static List<Person> people = new List<Person>();
        public static List<Person> People
        {
            get
            {
                return people;
            }
        }
        public static Person Get(string name)
        {
            //正常逻辑
            //var per = People.FirstOrDefault(o => o.Name == name);
            //if(per==null)
            //{
            //    per = new Person();
            //    per.Age = 23;
            //    per.Name = name;
            //    per.BinaryData = File.ReadAllBytes(@"D:\2.zip");//一个38MB的文件
            //    people.Add(per);
            //}
            //return per;
            //错误逻辑
            Person p = new Person();
            p.Age = 23;
            p.Name = name;
            p.BinaryData = File.ReadAllBytes(@"D:\2.zip");//一个38MB的文件
            people.Add(p);
            return p;
        }
    }
}

1. 捕获内存快照

程序运行起来后,从下图可以看到内存占用大约在18M左右,此时点击“截取快照”新建一个快照: 
这里写图片描述

然后click按钮5次,然后再点击 截图快照 ,如下: 
这里写图片描述 
从曲线图中可以明显看到内存从18M逐步增长到227M。第二次快照与第一次相比对象增加了19个,堆上的内存使用增加了191M左右。 
这里写图片描述 
点击图中的(+19)字样,可以进入到快照详细对比界面。

2. 详细对比

这里写图片描述 
如图所示,我们先来看下选中的那一行是什么意思: 
这里写图片描述 
可以看到MemLeak.Person这个对象的计数差异为+5,计数是5。表示这两次快照之间这个Person对象从0个变成了5个增加了5个。大小差异是增加了195601040个字节,这几个从字面意思看都很好理解。唯一有点难度的可能是非独占大小(字节),一眼看不出来是干什么的。但是我们注意到了List<MemLeak.Person>对象的大小是68字节而非独占大小却是195601108字节。 
非独占大小的统计方式如下: 
这里写图片描述

其中最下层的stringbyte[],由于没有子节点了,所以它们的非独占大小就是它们本身的大小。Address的非独占大小=自己本身的大小+String的非独占大小(50字节)+Byte[]的非独占大小(100字节)=200字节。Customer的非独占大小=自己本身(100字节)+String的非独占大小(100字节)+Address的非独占大小(200字节)=400字节。 
所以这也就解释了List<MemLeak.Person>,自身大小仅是68字节,但是由于它是一个List内部包含了5个Person对象,所以它的非独占大小就是5个Person对象大小的和。

3. 哪里产生了泄漏?

这里我先将过滤器里“仅我的代码”和“折叠小型对象类型”前面的勾去掉,便于分析。 
这里写图片描述 
因为我们值需要看当前程序集(MemLeak)的内存信息,所以在搜索类型名的框里直接输入我们程序集的名称,将其他一些没用的过滤掉。 
这里写图片描述 
是不是清爽了很多! 
分析内存当然要拿占用内存增多最大的对象开刀,点击MemLeak.Person,在下面的“根的路径”视图中可以看到增加的5个Person对象是在一个Person[]数组中。 
这里写图片描述 
但是我们好像并没有声明过这样的数组,所以继续展开: 
这里写图片描述 
原来是在一个List里面,而且这个List还是静态的。这时候就要想到内存泄漏的原因,可能就是代码哪里的静态变量持续占有应该释放的对象了。然后更改为正确代码就OK了。

4. 查看对象中哪种数据类型占用内存较多?

切换到的引用选项卡(“引用的类型”)。 
这里写图片描述 
可以看到本例中PersonByte[]类型的占用内存较多,所以我们就可以重点排查Person中哪里用到了Byte[]类型,进而去优化。

当初取消勾选的那两个选项是什么?

第一个是“仅我的代码”: 
勾上之后,会过滤掉那些.Net Runtime产生的一些对象和一些很常见的系统认为与你的程序无关的一些对象。

第二个是:“折叠小的对象类型”: 
勾上后,会过隐藏掉那些非独占大小小于托管堆总大小0.5%的对象。将这些小对象的非独占大小合并的父节点中。如下: 
这里写图片描述

这两个选项默认都勾上即可。

其它

对于生产环境中的软件,分析内存时可以多次抓取dump文件,用vs打开后分析比对。 
这里写图片描述 
不过只有vs的企业版才有这个功能。


参考

Memory Usage Tool while debugging in Visual Studio 2015

分类: .NET-VS 技巧

<think>好的,我现在需要帮助用户解决如何使用Visual Studio和VLD工具检测多线程程序中的内存泄漏问题。首先,我需要回顾用户提供的引用内容,看看有没有相关的信息。引用1提到单元测试和IDE工具,引用2和3具体提到了VLD的使用示例,包括在main函数中的初始化和报告泄漏的方法。用户的问题是多线程环境下的检测,这可能涉及到线程安全或VLD在多线程中的配置。 首先,我应该确定VLD是否支持多线程程序。根据引用3,用户提供的代码中在MFC应用中使用VLD,可能涉及多线程的情况,但需要确认VLD是否自动处理多线程。可能需要指导用户正确配置VLD,并确保在程序退出前生成报告。同时,多线程中的内存泄漏可能因为资源未正确释放,需要结合测试方法,如压力测试,来模拟高负载情况,如引用1中提到的压力测试。 接下来,我需要整理步骤:安装VLD、配置项目、添加检测代码、运行测试(包括多线程场景)、分析结果。另外,注意事项部分需要强调线程安全、及时释放资源、多次测试等。可能还需要提醒用户关于动态库加载顺序的问题,如引用3中的代码是否需要在所有线程启动前初始化VLD。 此外,用户可能需要了解如何编写多线程测试案例,确保每个线程分配和释放内存,以及如何处理静态变量导致的泄漏。最后,生成相关问题,帮助用户深入理解,比如如何结合单元测试或其他工具来补充VLD的检测。</think>### 使用Visual Studio和VLD检测多线程内存泄漏的方法 #### 1. **环境配置** - **安装Visual Leak Detector (VLD)** 从[官方仓库](https://github.com/KindDragon/vld)下载并安装VLD,确保与Visual Studio版本兼容。 - **项目配置** 在Visual Studio中: - 添加VLD头文件路径:`项目属性 → C/C++ → 常规 → 附加包含目录`; - 链接VLD库文件:`项目属性 → 链接器 → 输入 → 附加依赖项`中添加`vld.lib`; - 复制`vld_x64.dll`(或`vld_x86.dll`)到可执行文件目录[^2]。 #### 2. **代码集成** - 在程序入口(如`main`或`WinMain`)**最顶部**包含VLD头文件并初始化: ```cpp #include <vld.h> int main() { VLDGlobalEnable(); // 启用VLD // 多线程代码... VLDReportLeaks(); // 输出泄漏报告(可选) VLDGlobalDisable(); // 关闭VLD return 0; } ``` 注意:`VLDReportLeaks()`可主动触发报告,但程序退出时VLD会自动生成[^3]。 #### 3. **多线程检测要点** - **线程安全设计** VLD支持多线程检测,但需确保: - 所有线程的**内存操作**均在VLD初始化后执行; - 避免跨线程的**未同步内存管理**(如A线程分配、B线程释放)。 - **测试用例设计** - 通过**压力测试**模拟高并发场景,例如创建多个线程反复分配/释放内存; - 使用`std::thread`或`OpenMP`编写测试代码,观察VLD报告是否捕获泄漏[^1]。 #### 4. **泄漏分析与解决** - **查看输出窗口** 运行程序后,Visual Studio的“输出”窗口会显示泄漏详情,包括: - 泄漏内存块地址、大小; - 分配内存的调用堆栈(需确保编译时生成调试符号`.pdb`文件)[^2]。 - **常见问题定位** - **静态变量泄漏**:检查全局或静态对象的析构是否未释放资源; - **条件分支未释放**:确保所有代码路径(如异常、`if-else`)均有释放逻辑。 #### 5. **注意事项** - **动态库加载顺序**:若使用动态加载的DLL,确保VLD初始化早于DLL中内存操作; - **性能影响**:VLD会轻微增加内存和CPU开销,建议仅在调试阶段启用; - **结合其他工具**:可配合Visual Studio自带的“诊断工具”(`Debug → Windows → Diagnostic Tools`)进行实时内存监控。 --- ### 示例代码(多线程测试) ```cpp #include <vld.h> #include <thread> #include <vector> void thread_task() { int* ptr = new int(10); // 模拟泄漏(未释放) // delete ptr; // 修复需取消注释 } int main() { VLDGlobalEnable(); std::vector<std::thread> threads; for (int i = 0; i < 5; ++i) { threads.emplace_back(thread_task); } for (auto& t : threads) { t.join(); } VLDReportLeaks(); VLDGlobalDisable(); return 0; } ``` 输出结果会显示5个`int`类型的泄漏(每个线程分配1次未释放)。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值