好的,实际上这个过程比你写的要复杂一些。其中一些步骤包括更多内部步骤,比如第一步以及PE的加载(和mscoree.dll本身的加载)。
但是,我会尝试回答你的问题。只是请注意,你的问题有点大,所以我尽量简短回答。但是,如果你真的对此感兴趣,我强烈建议你阅读《CLR via C#》(Richter著)。他在第一章中讨论了加载过程,并为垃圾收集器专门设置了一章。
还有一些关于垃圾收集器基础的好的MSDN文章,你可能会觉得很有趣。
“创建了多少个堆栈、堆和线程?执行可执行文件中代码的线程是如何创建的?”
简单(空)控制台应用程序将有3个线程:主线程、GC线程和终结器线程。当然,每个线程都有自己的堆栈(每个堆栈为1MB)。
堆的数量取决于你使用的GC类型。如果你使用工作站GC(默认),则会有1个托管堆(具有2个段,一个用于“普通”对象,另一个是大型对象堆段)。
如果你使用服务器GC,则系统中每个逻辑核心都会有一个堆(每个堆都有两个段)。
“初始分配的内存大小是多少?是操作系统还是CLR分配内存?”
初始内存包含不止一个元素:每个线程都有1MB的堆栈,加载到进程中的映像的大小(当然取决于你的应用程序),以及“动态”元素的大小——你在应用程序中进行的分配,这会导致GC增加堆的大小,并且你不再使用的对象将被GC清理并可能导致GC释放内存。
“如果exe运行时需要更多内存,谁决定分配多少内存和何时分配?”
如果你有一个简单的控制台应用程序,在Main中创建一个新类的实例。在这种情况下,“new”关键字(CIL“newobj”指令)将使CLR计算所需的内存量。
如果一代中有足够的内存(新创建的对象存储在其中),则不会进行额外的内存分配。如果没有足够的内存,则GC将启动并调用VirtualAlloc为对象分配内存。在这种情况下,新创建的对象的引用将保存在堆栈上。
当然,引用保存的位置(堆栈、堆、处理器寄存器)以及对象分配的位置(堆栈/堆)可能会有所不同。基本上,这取决于分配类或结构的情况,以及分配的上下文是什么(如果它在方法内部,作为其他类的字段,在结构中的字段等)。它还可能因平台而异。
如果exe运行时需要更多的内存,是谁决定分配多少内存以及何时分配这些内存?
所有新对象的内存分配都由CLR本身管理(当然,CLR使用Windows API,如VirtualAlloc和VirtualFree,而Windows则管理虚拟内存)。
当您使用“new”运算符创建应在托管堆中创建的新对象时,CLR会计算所需的分配大小(所有字段的大小+锁定对象所需的一些开销和了解其类型),并查看托管堆中是否有可用空间(在第0代,CLR始终保持指向应分配新对象的位置的指针)。如果有,则使用它。否则,如果没有足够的内存,则垃圾收集开始,并且有时(取决于gc过程后的内存状态和其他一些东西),CLR将调用VirtualAlloc为进程分配更多内存。
当您关闭exe时会发生什么?CLR会在卸载App Domain(关闭exe)之前运行任何GC吗?还是操作系统?
CLR在卸载任何应用程序域之前运行快速GC。这个快速GC的目的是让finalizes有机会运行。关闭进程时,CLR不需要清理内存,因为操作系统会自动处理。希望能帮到您。