
程序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 lmustart end module name00040000 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.dll77c90000 77c99000 LPK (deferred)77cb0000 77cce000 IMM32 (deferred)0:000 !DumpDomain--------------------------------------System Domain: 64ffd058LowFrequencyHeap: 64ffd07cHighFrequencyHeap: 64ffd0c8StubHeap: 64ffd114Stage: OPENName: None--------------------------------------Shared Domain: 64ffc9a8LowFrequencyHeap: 64ffc9ccHighFrequencyHeap: 64ffca18StubHeap: 64ffca64Stage: OPENName: NoneAssembly: 00d867a8--------------------------------------Domain 1: 00d41bd8LowFrequencyHeap: 00d41bfcHighFrequencyHeap: 00d41c48StubHeap: 00d41c94Stage: OPENSecurityDescriptor: 00d42f00Name: ConsoleApplication1.exeAssembly: 00d867a8 [C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll]ClassLoader: 00d86828SecurityDescriptor: 00d7a7b8Module Name634b1000 C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dllAssembly: 00d8fc60 [C:\Users\Administrator\Documents\Visual Studio 2010\Projects\ConsoleApplication1\ConsoleApplication1\bin\Release\ConsoleApplication1.exe]ClassLoader: 00d91680SecurityDescriptor: 00d8fb60Module Name00d12c5c C:\Users\Administrator\Documents\Visual Studio 2010\Projects\ConsoleApplication1\ConsoleApplication1\bin\Release\ConsoleApplication1.exe0:000 !Dumpmodule 00d12c5cNo export Dumpmodule found0:000 !DumpModule 00d12c5cName: C:\Users\Administrator\Documents\Visual Studio 2010\Projects\ConsoleApplication1\ConsoleApplication1\bin\Release\ConsoleApplication1.exeAttributes: PEFileAssembly: 00d8fc60LoaderHeap: 00000000TypeDefToMethodTableMap: 022f0010TypeRefToMethodTableMap: 022f07ecMethodDefToDescMap: 022f083cFieldDefToDescMap: 02352a98MemberRefToDescMap: 02352aa4FileReferencesMap: 02352af8AssemblyReferencesMap: 02352afcMetaData start address: 00073d94 (10797316 bytes)0:000 db 00073d94 l 100000073d94 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.........%300073e14 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应用程序的内存负荷。