一个.NET应用程序的生命周期是什么?

7

拥有一个简单的.NET .exe应用程序。一旦执行,它的生命周期是什么?我的理解是以下内容发生:

> 1. OS loads exe assemblies into memory
> 2. OS checks if it is in fact .net assembly
> 3. mscoree.dll loads, and loads CLR
> 4. CLR takes over and loads external dlls, GC stuff, memory management etc.
> 5. CLR creates app domain where exe assemblies are loaded
> 6. exe is started

如果以上内容正确(请随意详细说明),我对最后一步感兴趣,即CLR加载程序集时。

  1. 创建了多少个堆栈、堆和线程?是在可执行文件中创建并执行代码的线程吗?
  2. 初始分配的内存大小是多少?是操作系统还是CLR分配内存?
  3. 它如何知道最初需要多少内存?
  4. 如果在运行exe时需要更多内存,谁决定分配多少内存以及何时分配?
  5. 当您关闭exe时会发生什么?CLR是否在卸载应用程序域(关闭exe)之前运行任何GC,还是由操作系统处理?

5
只需购买《CLR via C#》这本书或者阅读博客系列《CLR Inside Out》即可。这个问题过于宽泛,我很确定你的每一个问题之前都已经被回答过了。 - CodeCaster
2个回答

8
创建了多少个堆栈、堆和线程?在你的应用程序运行的过程中,可能会有很多线程,但其中一个是执行的主线程。当创建一个线程时,它会分配一个1MB的堆栈。

谁分配内存(操作系统还是CLR)?如此处所述,公共语言运行库的垃圾回收器管理应用程序的内存分配和释放。这是托管和非托管程序之间的重要区别。如果您已经使用C或C++编写过程序,则一定知道这个责任属于开发人员而不是垃圾回收器。这是一个伟大的力量,但伴随着伟大的力量而来的是伟大的责任。您需要负责找到(创建)所需的内存空间。然后将对象分配到那里,当您不再需要它时,必须释放这个内存。上述过程中可能出现的任何错误都会导致内存泄漏,甚至导致程序崩溃。更不用说调试这种错误的困难了。

另一方面,在托管程序的世界中(如C#、Java、JavaScript等),这个责任属于一个称为垃圾回收器的运行时的一部分。垃圾回收器分配内存,并决定何时适当地启动并清除垃圾。这本身就是极大的便利,显著地简化了开发人员的工作。但这是一种权衡考虑。托管程序无法达到良好结构的非托管程序的性能。

当您关闭可执行文件时,在卸载应用程序域之前,将执行垃圾收集以释放任何已使用的资源。然后将卸载应用程序域。在此之后,CLR将从进程的内存空间中分离出来,最后终止该进程。

4
好的,实际上这个过程比你写的要复杂一些。其中一些步骤包括更多内部步骤,比如第一步以及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不需要清理内存,因为操作系统会自动处理。希望能帮到您。

假设您的代码中有新的Dictionary<K,T>(),主线程(CLR)会考虑它吗?会增加堆的大小吗?在读取新的Dict行之前,堆的初始大小是多少? - ShaneKm
修改了我的回答,希望它能更好地回答问题... - Shahar Gvirtz
如果您正在使用服务器GC,则系统中每个逻辑线程都会有1个堆。我猜您指的是每个逻辑核心一个堆。 - Yuval Itzchakov
Yuval,当然。我修正了我的答案。 - Shahar Gvirtz
1
@eaglei22 这个答案基于之前阅读书籍(例如《CLR via C#》)、博客文章等所获得的知识,没有任何魔法涉及 :) - Shahar Gvirtz
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接