CLR在哪里存储静态类?

9

这里有很多人感到困惑,

普通类将其数据存储在堆中,对栈的引用(指针)在堆上。

当栈超出范围时,下一次垃圾收集器启动并从堆中删除内存。

现在,在静态类的情况下,由于需要整个程序中都存在,因此垃圾收集器无法清除内存。而且首先无法获取引用的方式。

那么例如我们调用Console.Write,程序从哪里获取它的引用呢(它将静态类的引用存储在哪里)?或者它直接调用它,但是如何执行呢?


4
我不理解这个问题的任何部分。你所说的“它的值”和“它的引用”是什么意思? - Eric Lippert
我认为他在谈论它如何将可执行代码与数据成员分离。 - Joel Coehoorn
1
除了其他问题外,这是“它的”。 - jason
那么,你是在问静态类的引用存储在哪里,它是从哪里引用的吗? - Charlie
我想问一下,静态类的引用存储在哪里。 - Athiwat Chunlakhan
2个回答

17
我觉得你把类和内存位置混淆了,以及内存如何被保持的方式。当你创建一个普通类的实例时,该实例的内存位于堆上。对这个实例的引用可能在堆上的对象中(如果你在不同对象的内部设置了成员变量),也可能在栈变量中(如果你在方法内部声明了一个变量并将其传递给函数调用),或者可能在全局根列表中(如果它是静态引用,例如单例引用)。
静态类不能被实例化。在任何地方都没有“引用”到类(除了类型信息)。它的方法只是在CLR加载程序集时加载到内存中的函数。你可以创建一个委托,指向其中的一个方法,但这并不会创建一个类实例的引用。那只是一个指向函数的指针。
例如,看看这段代码:
class ObjectWrapper
{
    Object obj = new Object();
}

static void Main(string[] args)
{
    ObjectWrapper wrapper = new ObjectWrapper();
    ...
}

Main方法创建一个ObjectWrapper类的实例。这个实例存在于堆中。

在ObjectWrapper实例内部,有一个存活于堆上的Object类的实例。对这个类的引用在实例内部,因此可以将引用视为“存在于堆中”。

现在,将其与以下代码进行比较:

class Singleton
{
    static readonly instance = new Singleton();
}

单例对象的实例也存储在堆上。然而,引用是静态引用,并由CLR保存在全局或“根”引用列表中。

现在看看这个静态类:

class ObjectWrapper
{
    Object obj = new Object();
}

static class HelperMethods
{
    static int DoSomethingUseful(ObjectWrapper wrapper1)
    {
        ObjectWraper wrapper2 = wrapper1;
        // code here
    }
}

HelperMethods是一个静态类,你不能实例化HelperMethods类。在堆上不能有任何来自此类的对象。但是,在DoSomethingUseful方法中,它有两个对ObjectWrapper类实例的引用存在于栈上。一个被传递进来,另一个在方法内部声明。


就“根”而言,可以查看这篇文章,了解.NET的GC算法,并澄清根在GC中的作用。 http://msdn.microsoft.com/en-us/magazine/bb985010.aspx - felideon
这个答案中有几个点并不完全准确。首先,“没有任何地方引用它”这个说法是不正确的。每种类型,无论是静态的还是其他的,都在加载器堆上保留了一个引用。整个类型系统都在加载器堆上管理,包括对类型及其成员的引用。此外,“这个类永远不会在堆上消耗内存”也是不正确的……虽然它不会消耗GC堆空间,但它确实会在加载器堆上消耗堆空间。关于引用只是指针的想法也是不正确的……CLR使用多层间接。 - jrista
请查看我在答案中链接的文章,以充分了解类型系统。它不像C或C++那样简单,有不止一个堆,有不止一种堆,"指针"比"引用"更少见,而"引用"通常会间接多次引用,才能真正到达引用所指的"东西",而"静态"类根本不在"堆"上。根据其结构,静态类可能在几个堆上拥有自己的部分。 - jrista

7
简单地说,静态类被“存储”在所谓的装载器堆上。装载器堆是特殊的非GC堆,具有极其可预测和严格的增长速率。当.NET应用程序启动时,实际上会创建几个AppDomain。除了主要的应用程序域之外,还有包含系统命名空间和mscorelib、特殊堆(如装载器堆)以及CLR本身的系统和共享应用程序域。

要获得详细的解释,请阅读以下MSDN Magazine文章:

深入了解.NET Framework内部,看看CLR如何创建运行时对象

尽管这篇文章写于几年前,但仍然适用。(不过我无法确定.NET 4.0是否有太大变化。)


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