关于.net反射和metadata加载--致Jeffray Zhao等几位和firelong

看了firelong写的C#会重蹈覆辙吗?系列之2:反射及元数据的性能问题, Ivony写的C#呓语:谁说程序都要加载到内存?和后面的很多评论后,觉得需要写点来表达一些观点。希望能同大家一起探讨。

firelong在C#会重蹈覆辙吗?系列之2:反射及元数据的性能问题中提及:

" 程序(EXE/DLL)最后都是要加载到内存中运行的,不是光放在硬盘上的——这也是为什么.NET程序占用内存都超多"

有几位园友在评论中指出这句是错误的或者质疑firelong的结论。如:

Jeffray Zhao: "不用,不JIT的话,是不会把整个dll加载到内存中的,而是用多少加载多少,这点已经讨论了很多遍了"

道法自然:".NET类加载器,在一个方法一个方法循环调用时,仅是在一个方法调用前,才会加载这个方法使用的类型。这一个是类型加载的时机。另一个问题,类型加载时,包含了什么内容?也就是说,加载类型时,都加载了什么样的元数据?"

 

1. 关于.net程序集加载

 

虽然在.net方面工作过几年,但是说实话在此文之前我并没有对.net程序集加载有一个清晰的概念。这些个园友都是些有经验的人,为什么他们说的观点相反。到底.net程序集加载是整个地加载?还是如Jeffray Zhao等所说"用多少加载多少", "用到那个类型就加载该类型"。刚好最近在看<CLR via c#> 第三版英文版,试图在书中找到答案。但是有点失望的是,书中并没有很详细地说。只是有一段提及了.net程序集加载,然后就不再深入说了。Jefferay Zhao等几位所持的观点没有办法在书中得到验证。只好自己来验证。刚好Ivony写了C#呓语:谁说程序都要加载到内存?,我就接着他的例子去做一下吧。Ivony的文章结论是没有用到的metadata是不会加载到内存。我可以重复一下Ivony的实验,看看我是否也得出同样的结论(有点象物理化学的实验, 是不是?)。

我首先照Ivony说的过程重复了一下他做过的步骤,产生了一个巨大的Test.cs文件,里面包含100个类,100个方法,100个属性,发现确实如他所说:在任务管理器里面看那个ConsoleApplication1.exe占用只有1MB 多一点的内存空间。而编译出来的ConsoleApplication1.exe有10MB。初步看起来,Console1Application1.exe没有全部加载进内存,也即100个类的metadata没有加载进内存。

此后两天我都在想为什么呢?真如他们几位所说,那么CLR具体是如何在内存里面组织这些metadata,类型,是如何JIT编译方法的。想不透,只好祭出强大的工具: Windbg来找找答案。这回有一些不同的发现:

 

0:000> lmu
start    end        module name
00040000 00ac4000   ConsoleApplication1   (deferred)            
634b0000 63fa8000   mscorlib_ni   (deferred)            
64ab0000 65040000   mscorwks   (deferred)            
6b040000 6b0a6000   mscoreei   (deferred)            
6e2a0000 6e2fb000   mscorjit   (deferred)            
6e570000 6e5ba000   mscoree    (deferred)            
73950000 739eb000   MSVCR80    (deferred)            
74db0000 74f4e000   comctl32   (deferred)            
76640000 766ea000   msvcrt     (deferred)            
766f0000 7673b000   GDI32      (deferred)            
76740000 7681c000   KERNEL32   (deferred)            
76820000 76879000   SHLWAPI    (deferred)            
76880000 76946000   ADVAPI32   (deferred)            
769a0000 76a3d000   USER32     (deferred)            
76a40000 76b03000   RPCRT4     (deferred)            
76b10000 77620000   shell32    (deferred)            
777b0000 77878000   MSCTF      (deferred)            
77880000 779c5000   ole32      (deferred)            
779d0000 77a4d000   USP10      (deferred)            
77b40000 77c67000   ntdll      (export symbols)       C:/Windows/system32/ntdll.dll
77c90000 77c99000   LPK        (deferred)            
77cb0000 77cce000   IMM32      (deferred)            
0:000> !DumpDomain
--------------------------------------
System Domain: 64ffd058
LowFrequencyHeap: 64ffd07c
HighFrequencyHeap: 64ffd0c8
StubHeap: 64ffd114
Stage: OPEN
Name: None
--------------------------------------
Shared Domain: 64ffc9a8
LowFrequencyHeap: 64ffc9cc
HighFrequencyHeap: 64ffca18
StubHeap: 64ffca64
Stage: OPEN
Name: None
Assembly: 00d867a8
--------------------------------------
Domain 1: 00d41bd8
LowFrequencyHeap: 00d41bfc
HighFrequencyHeap: 00d41c48
StubHeap: 00d41c94
Stage: OPEN
SecurityDescriptor: 00d42f00
Name: ConsoleApplication1.exe
Assembly: 00d867a8 [C:/Windows/assembly/GAC_32/mscorlib/2.0.0.0__b77a5c561934e089/mscorlib.dll]
ClassLoader: 00d86828
SecurityDescriptor: 00d7a7b8
  Module Name
634b1000 C:/Windows/assembly/GAC_32/mscorlib/2.0.0.0__b77a5c561934e089/mscorlib.dll

Assembly: 00d8fc60 [C:/Users/Administrator/Documents/Visual Studio 2010/Projects/ConsoleApplication1/ConsoleApplication1/bin/Release/ConsoleApplication1.exe]
ClassLoader: 00d91680
SecurityDescriptor: 00d8fb60
  Module Name
00d12c5c C:/Users/Administrator/Documents/Visual Studio 2010/Projects/ConsoleApplication1/ConsoleApplication1/bin/Release/ConsoleApplication1.exe

0:000> !Dumpmodule 00d12c5c
No export Dumpmodule found
0:000> !DumpModule 00d12c5c
Name: C:/Users/Administrator/Documents/Visual Studio 2010/Projects/ConsoleApplication1/ConsoleApplication1/bin/Release/ConsoleApplication1.exe
Attributes: PEFile
Assembly: 00d8fc60
LoaderHeap: 00000000
TypeDefToMethodTableMap: 022f0010
TypeRefToMethodTableMap: 022f07ec
MethodDefToDescMap: 022f083c
FieldDefToDescMap: 02352a98
MemberRefToDescMap: 02352aa4
FileReferencesMap: 02352af8
AssemblyReferencesMap: 02352afc
MetaData start address: 00073d94 (10797316 bytes)
0:000> db 00073d94 l 1000
00073d94  42 53 4a 42 01 00 01 00-00 00 00 00 0c 00 00 00  BSJB............
00073da4  76 32 2e 30 2e 35 30 37-32 37 00 00 00 00 05 00  v2.0.50727......
00073db4  6c 00 00 00 30 91 2c 00-23 7e 00 00 9c 91 2c 00  l...0.,.#~....,.
00073dc4  54 2e 78 00 23 53 74 72-69 6e 67 73 00 00 00 00  T.x.#Strings....
00073dd4  f0 bf a4 00 1c 00 00 00-23 55 53 00 0c c0 a4 00  ........#US.....
00073de4  10 00 00 00 23 47 55 49-44 00 00 00 1c c0 a4 00  ....#GUID.......
00073df4  e8 00 00 00 23 42 6c 6f-62 00 00 00 00 00 00 00  ....#Blob.......
00073e04  02 00 01 10 57 15 a2 01-09 00 00 00 00 fa 25 33  ....W.........%3
00073e14  00 16 00 00 01 00 00 00-13 00 00 00 f6 01 00 00  ................
00073e24  02 00 00 00 96 88 01 00-51 c3 00 00 13 00 00 00  ........Q.......
00073e34  0d 00 00 00 01 00 00 00-f4 01 00 00 50 c3 00 00  ............P...
00073e44  50 c3 00 00 01 00 00 00-01 00 00 00 00 00 0a 00  P...............

......省略很多

注意看我用黄色背景加亮的那些数字。前两个数字是这个ConsoleApplication1.exe所在的地址。注意其长度。后两个是metadata的起始地址及长度。注意其长度是大约10MB。

以上的Windbg记录显示我们的程序集虽然有10MB之巨,但是.net CLR还是将该程序集全部载入内存。metadata也随该程序集一起被CLR载入内存。不管是用到了类型的metadata,还是没有用到类型的metadata,都被载入了内存。这样就很清晰了。至少firelong这半句话是对的: "程序(EXE/DLL)最后都是要加载到内存中运行的,不是光放在硬盘上的", 那么为什么任务管理器显示ConsoleApplication1.exe只占用1MB左右的内存呢?这个问题有好几天我都没有办法解释。只到今天,我突然想起来用其他工具来查看进程的内存占用。结果令我恍然大悟: 原来任务管理器统计的内存占用不准确。那么好了,结论就是程序集是整个地被加载进内存的,不是"用多少加载多少", "用到那个类型就加载该类型".

 

2. 关于firelong所说的这后半句话:"这也是为什么.NET程序占用内存都超多"

 

firelong认为: 我们自己编译出来的程序集, metadata占太大比例的空间, 有50%以上。.net FCL本身的程序集metadata也占比较大的空间. metadata对性能影响很大。 我给解释一下:

    我们自己编译出来的程序集,  metadata的大小取决于设计,开发者设计了很多类型,那metadata自然小不了。你想想有没有过度设计,减少点设计复杂度,类型少一点,metadata的尺寸自然会小点。

    .net FCL本身的程序集,  我们可以先说说mscorlib.dll, 它是每一个.net应用程序必引用的. 它大约就5M左右,它是运行每一个.net应用程序必须的负载(overhead), 你的机器不会连5M内存消耗都承受不起吧。 至于其他的.net FCL的程序集, 是用到了才会加载,不是必须的。.net FCL设计已经相当精炼了。另外还有一个.net CLR提供的特性可以帮助减少.net 应用程序的内存消耗:domain neutral, 有domain neutral特性的程序集都是跨AppDomain共享的,那么在一个进程的内存里面只要一份mscorlib.dll的拷贝就行了。.net FCL的程序集都是domain neutal的。还有asp.net下我们的应用程序集也是domain neutral的. 通过这些手段, 微软.net团队已经大大减少了.net应用程序的内存负荷。

     另外,大家可以注意一下你的应用程序里面的线程,有时候线程多也是造成内存占用大的原因。

     总之firelong认为metadata过大影响性能是站不住脚的,实际上那只与你的设计有关。firelong将.net平台, c#编程语言与c/c++相比,并不合适. 这些编程平台各自有各自的特点,结合自己真实的需要才是正确的。

 

2010.7.7.注:

在cnblogs帖出此文后,关注者不少。提出了质疑,我又再验证,获得了更多的信息(!address命令的输出):

 

  BaseAddr EndAddr+1 RgnSize     Type       State                 Protect             Usage
-------------------------------------------------------------------------------------------
*        0    10000    10000             MEM_FREE    PAGE_NOACCESS                      Free
*    10000    20000    10000 MEM_MAPPED  MEM_COMMIT  PAGE_READWRITE                     MemoryMappedFile "PageFile"
*    20000    21000     1000 MEM_PRIVATE MEM_COMMIT  PAGE_READWRITE                     <unclassified>
*    21000    30000     f000             MEM_FREE    PAGE_NOACCESS                      Free
*    30000    34000     4000 MEM_MAPPED  MEM_COMMIT  PAGE_READONLY                      MemoryMappedFile "PageFile"
*    34000    40000     c000             MEM_FREE    PAGE_NOACCESS                      Free
*    40000    41000     1000 MEM_MAPPED  MEM_COMMIT  PAGE_READONLY                      MemoryMappedFile "PageFile"
*    41000    50000     f000             MEM_FREE    PAGE_NOACCESS                      Free
*    50000    91000    41000 MEM_MAPPED  MEM_COMMIT  PAGE_READONLY                      MemoryMappedFile "PageFile"
*    91000    a0000     f000             MEM_FREE    PAGE_NOACCESS                      Free
*    a0000    e1000    41000 MEM_MAPPED  MEM_COMMIT  PAGE_READONLY                      MemoryMappedFile "PageFile"
*    e1000    f0000     f

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
metadata-generation-failed是一个错误,它表示在生成软件包元数据时出现了问题。这个错误通常是由于一些原因导致的,比如网络连接问题、软件包源不可用或软件包的依赖关系错误等。当metadata-generation-failed错误发生时,需要检查以下几个方面: 1. 网络连接:确保你的网络连接正常,可以尝试使用其他网络连接或者检查网络设置。 2. 软件包源:检查你使用的软件包源是否可用,可以尝试切换到其他可靠的软件包源。 3. 依赖关系:检查软件包的依赖关系是否正确,可能是某个依赖项缺失或版本不兼容导致的错误。 4. 缓存问题:有时候,清除软件包缓存可以解决metadata-generation-failed错误。你可以尝试清除缓存并重新运行命令。 subprocess-exited-with-error是一个错误,它表示子进程在执行过程中出现了错误。这个错误通常是由于子进程执行的命令或脚本出现了问题导致的。当subprocess-exited-with-error错误发生时,需要检查以下几个方面: 1. 命令或脚本错误:检查你执行的命令或脚本是否正确,可能是命令拼写错误、脚本语法错误或脚本逻辑错误导致的错误。 2. 输入输出问题:检查子进程的输入和输出是否正确,可能是输入数据格式错误或输出数据处理错误导致的错误。 3. 环境配置:检查子进程执行所需的环境配置是否正确,可能是环境变量设置错误或依赖项缺失导致的错误。 4. 日志和错误信息:查看子进程的日志和错误信息,以便更好地理解错误的原因和解决方法。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值