在我开始讨论这个真正的问题之前,我只想说我可能会在这里弄错一些细节。如果是这样,请逮捕我,甚至不要回答我的问题。
我的问题基本上是关于DLL和.NET的。我们有一个使用相当多内存的应用程序,我们正试图弄清楚如何正确地测量它,特别是当问题主要发生在客户端的计算机上时。
有一点让我感到震惊的是,我们有一些相当大的.NET程序集和生成的ORM代码。
如果我使用的是具有唯一基址的非托管(Win32)DLL,则同一台计算机上的多个并发进程会将DLL加载到物理内存中,并将其映射到所有应用程序的虚拟内存中。因此,物理内存将为此DLL使用一次。
问题是.NET程序集会发生什么。这个DLL包含IL,虽然它的这一部分可能在应用程序之间共享,但是这个IL产生的JITted代码呢?这是共享的吗?如果没有,我如何衡量以解决这个问题实际上是否导致问题? (是的,我知道,它会有所贡献,但我不会花太多时间在这上面,直到这是最大的问题)。
另外,我知道我们没有查看解决方案中所有.NET程序集的基址,.NET程序集是否有必要这样做?如果是的话,是否有一些关于如何确定这些地址的指南?
对这一领域的任何见解都会受到欢迎,即使事实证明这不是一个大问题,甚至根本不是问题。
修改:刚发现这个问题:.NET assemblies and DLL rebasing部分回答了我的问题,但我仍然想知道JITted代码如何影响所有这些。
从该问题及其接受的答案看来,JITted代码放在堆上,这意味着每个进程将加载共享二进制程序集映像,并在其自己的内存空间内生成代码的私有JITted副本
我们有什么方法可以衡量这一点吗?如果这会产生大量代码,我们必须更多地查看生成的代码,以确定是否需要调整它。
修改:在此处添加了一个较短的问题列表:
@Brian Rasmussen here的答案表明JITting将按照我的预期生成JITted代码的每个进程副本,但是对程序集进行重新定位实际上会对减少内存使用量产生影响。我将不得不深入研究他提到的WinDbg + SoS工具,我已经在我的列表上停留了一段时间,但现在我怀疑我不能再把它推迟了。)
修改:我在主题上找到了一些链接:
答案 0 :(得分:6)
这是针对问题1)
jitted代码放在特殊的堆上。您可以使用WinDbg + SoS中的!eeheap
命令检查此堆。因此,每个进程都有自己的jitted代码副本。该命令还将显示代码堆的总大小。
如果您需要有关从WinDbg获取此信息的其他详细信息,请与我们联系。
这是针对问题2)
根据书籍 Expert .NET 2.0 IL Assembly ,纯IL-PE PE文件的.reloc
部分仅包含一个CLR启动存根的修正条目。因此,在变基期间托管DLL所需的修正量相当有限。
但是,如果您列出任何给定的托管进程,您会注意到Microsoft已经重新设置了其托管DLL的批量(或可能全部)。是否应将其视为变基的原因取决于您。
答案 1 :(得分:3)
我不确定以下信息对于较新版本的.NET和/或Windows版本有多准确。自.NET早期以来,MS可能已经解决了一些DLL加载/共享问题。但我相信以下大部分内容仍然适用。
使用.NET程序集,进程之间(以及终端服务器会话之间)页面共享的许多好处都消失了,因为JIT需要动态编写本机代码 - 没有用于备份本机代码的映像文件。所以每个进程都有自己的,为jitted代码分开的内存页面。
这类似于由于DLL不正确而导致的问题 - 如果操作系统需要在加载时对标准Win32 DLL执行修正,则无法共享固定部分的内存页。
但是,即使无法共享jitted代码,重新绑定.NET DLL也有好处,因为仍然会为元数据(和IL)加载DLL - 如果不需要修复,则可以共享这些内容。 / p>
使用ngen可以帮助与.NET程序集共享内存页面。但这带来了一系列问题。
请参阅Jason Zander撰写的这篇旧博客文章,了解一些细节:
http://blogs.msdn.com/jasonz/archive/2003/09/24/53574.aspx
Larry Osterman有一篇关于DLL页面共享和修正效果的博客文章:
http://blogs.msdn.com/larryosterman/archive/2004/07/06/174516.aspx
答案 2 :(得分:0)
我认为你对共享程序集和dll以及进程内存空间感到困惑。
.NET和标准Win32 DLL在使用它们的不同进程之间共享代码。在.NET的情况下,这仅适用于具有相同版本签名的DLL,因此同一dll的两个不同版本可以同时加载到内存中。
看起来像你希望库调用分配的内存也可以共享,好,从不(几乎)发生。当库中的函数分配内存时,我想对于ORM DLL会发生很多事情,该内存在调用进程的内存空间内分配,每个进程都有唯一的数据实例。
所以是的,实际上DLL 代码被加载一次并在调用者之间共享,但代码指令(以及分配)分别发生在调用进程空间中。
修改强> 好的,让我们看看JIT如何与.NET程序集配合使用。
当我们谈论JITing代码时,过程相对简单。在内部有一个称为虚拟方法表的结构,它基本上包含将在调用期间调用的虚拟地址。在.NET中,JIT基本上编辑该表,以便每个调用都重定向到JIT编译器。这样,任何时候我们调用JIT进入的方法并将代码编译为实际的机器指令(因此Just In Time),一旦完成,JIT将返回VMT并替换调用的旧条目他指向生成的低级代码。这样,所有后续调用都将被重定向到已编译的代码(因此我们只编译一次)。因此,每次都不会调用JIT,所有后续调用都将重定向到相同的编译代码。对于DLL,这个过程可能是相同的(虽然我不能完全向你保证)。