助记符: 辅助您的( 虚拟) 内存






4.84/5 (10投票s)
一个用于可视化 Windows 进程使用的虚拟内存的工具。
引言
本文描述了我编写的一个工具,用于以图形和动态的方式显示 Windows 进程对虚拟内存的消耗。
背景
最近,我需要调查一个 Windows 进程消耗虚拟内存 (VM) 的方式。 我想在我的脑海中形成一个关于可用 VM 及其如何被进程分配、释放和映射,以及诸如虚拟内存碎片等现象的画面。
我遇到了 Charles Bailey 的这个工具:http://hashpling.org/asm/。 它运行良好并提供了帮助,但我想要更多关于特定类型分配的信息(那些对应于内存映射文件以及进程加载的托管和非托管程序集),并且我想能够自己理解“幕后”发生的事情。 所以我写了自己的工具。
使用工具
只需运行可执行文件即可。 它需要 .NET Framework 4 客户端配置文件,但不需要其他安装。
从下拉列表中选择一个正在运行的进程。 或者,键入进程的名称(例如“Excel”),并等待它启动; 助记符将自动扫描该进程,只要它在运行。
您将看到这样的屏幕

请注意,图表右侧的“刻度”显示了 3071 MB - 大约 3GB。 这是因为此屏幕截图是在 32 位 Windows 机器上运行的,并带有 /3GB 开关,它将用户模式虚拟地址空间扩展到近 3GB。 在标准 32 位 Windows 环境中,此限制为 2GB,而在 64 位 Windows 下,则为 4GB(因为 Chrome 是一个 Win32 进程)。
请注意右侧的大(大约 1GB)黄色区域,该区域被保留但未提交。 如果您在 32 位 Windows 下运行带有 /3GB 的 Win32 进程,或在 64 位 Windows 下运行,除非您的进程被标记为“大地址感知”,您将看到它。 如果您的进程使用 LARGEADDRESSAWARE
标记,则此区域将被标记为“空闲”(至少最初是这样); 如果没有,Windows 会保留它以防止该进程访问它。
将鼠标悬停在图表上以查看有关分配的详细信息,包括地址范围和在该范围内加载的任何模块(托管或非托管)。
按住 Control 键单击图表以将其另存为文件。 指定文件的根名称(和文件夹); 将追加一个序列号和 .png 扩展名。 只需单击图表即可保存后续快照; 序列号将递增。
Using the Code
该代码由一个可重复使用的用于枚举虚拟内存内容的类组成:VirtualMemoryExplorer
,以及一个简单的前端界面,允许选择进程和信息的图形表示。
VirtualMemoryExplorer.Scan
中发生了两个“枚举”循环,它们构建了感兴趣的 VM 区域列表及其特征。
第一个查看 VM 分配
// Use UInt32 so that we can cope with addresses above 2GB in a /3GB
// or "4GT" environment, or 64-bit Windows
UInt32 address = 0;
for (; ; address = (UInt32)m.BaseAddress + (UInt32)m.RegionSize)
{
if (0 == VirtualQueryEx(processHandle, (UIntPtr)address, out m,
(uint)Marshal.SizeOf(m)))
{
// Record the 'end' of the address scale
// (Expect 2GB in the case of a Win32 process running under 32-bit Windows,
// but may be extended to up to 3GB
// if the OS is configured for "4 GT tuning" with the /3GB switch
// Expect 4GB in the case of a Win32 process running under 64-bit Windows)
addressLimit = address;
break;
}
VMChunkInfo chunk = new VMChunkInfo();
chunk.regionAddress = (UInt32)m.BaseAddress;
chunk.regionSize = (UInt32)m.RegionSize;
chunk.type = (PageType)m.Type;
chunk.state = (PageState)m.State;
if ((chunk.type == PageType.Image) || (chunk.type == PageType.Mapped))
{
// .Net 4 maps assemblies into memory using the memory-mapped file mechanism;
// they don't show up in Process.Modules list
string fileName = GetMappedFileName(processHandle, chunk.regionAddress);
if (fileName.Length > 0)
{
fileName = Path.GetFileName(fileName);
chunk.regionName = fileName;
}
}
chunkInfos.Add(chunk);
};
第二个查看进程加载的模块 (DLL)。 我使用了 Process.Modules
方法。 如此页面所述,从 .NET 4 开始,此列表不再包含托管程序集 - 仅包含非托管程序集。 (发现托管程序集的唯一方法是结合使用 GetMappedFileName
和 VirtualQueryEx
,这在上面的代码片段中完成)。
mappingInfos = new List<VMRegionInfo>();
foreach (ProcessModule module in process.Modules)
{
VMRegionInfo mappingInfo = new VMRegionInfo();
mappingInfo.regionAddress = (UInt32)module.BaseAddress;
mappingInfo.regionSize = (UInt32)module.ModuleMemorySize;
mappingInfo.regionName = Path.GetFileName(module.FileName);
mappingInfos.Add(mappingInfo);
}
// Sort by address
mappingInfos.Sort(delegate(VMRegionInfo map1, VMRegionInfo map2)
{
return Comparer<UInt32>.Default.Compare(map1.regionAddress, map2.regionAddress);
});
关注点
我一直努力解决进程选择机制,它使用一个 DropDown
。 如果按下下拉按钮,我想显示正在运行的进程列表。 如果在 textbox
中键入名称,我希望助记符在匹配键入名称的进程启动后立即开始扫描。 为了实现我想要的行为,我最终选择在一个后台线程中扫描可用进程。
有趣的是,.NET 应用程序默认情况下未标记为 LARGEADDRESSAWARE
,并且主流应用程序(如 Office 2007 套件)也未如此标记。 我有兴趣知道为什么会这样。
我对在托管应用程序中使用内存映射文件(.NET 4 中对此技术有新的支持)的可能性很感兴趣,因此我很高兴看到 CLR 使用内存映射文件来访问它加载到进程中的程序集 (DLL)。 它们在助记符中显示为紫色,并且程序集名称显示在状态栏中。
历史
- 2011 年 11 月 24 日:第一个版本
- 2012 年 2 月 9 日:更新了代码,使其在 32 位和 64 位 Windows 下运行,并支持 32 位和 64 位进程