2016-07-27 31 views
32

我想了解C#如何管理内存。我被困在静态元素上,我阅读了许多关于这个主题的博客和文章,但是我找不到一个相当令人满意的答案。所有静态成员存储在哪里?

让我们定义一个代码块来帮助找到答案。

class myClass 
{ 
    static string myStr = "String Data"; 
    static int myInt = 12; 
} 

在你们分享你的答案之前,让我分享一下我对这个问题的了解。随意同意或不同意,并帮助我找到正确的答案。

  • 静态只是终身。
  • 静态引用类型(myStr)将在堆上生命期。
  • 静态值类型(myInt)将在堆栈中一直存在。

什么使我困惑,是我在互联网上找到的关于这个问题的一些答案。

混乱数1:

当你的程序启动时,它加载所有相关组件到一个AppDomain。加载程序集时,将调用所有静态构造函数,包括静态字段。他们将住在那里,并且卸载它们的唯一方法是卸载AppDomain。

在上面几行中,明确指出存储在AppDomain上的所有静态元素。那么为什么互联网上的每个人都说'静态'元素存储在堆/栈上?

混淆数2:

每个静态变量被存储在堆上,无论它是否是一个参考类型或值类型中声明。

如果每个静态变量存储在堆上。那么为什么有些人说,值类型的静态变量存储在堆栈中?

请帮助连接我的点,以了解C#中静态变量的内存管理。非常感谢您宝贵的时间:)

+15

人们首先通过拖拽“堆栈”和“堆”的概念,从而大大地混淆了自己。如果你想知道的只是对象的生命周期或声明的范围,这些概念对于它们本身并不有用,它们是C#中更相关的概念。垃圾收集意味着,95%的时间,你需要关心的是一个对象是否存在,并且只要该类被加载,一个由'static'字段引用的对象就存在。 (至于何时实例化,这是一个更复杂的话题。)当然,这不是一个答案。 –

+1

因为他们错了。他们知道值类型_local_变量存储在堆栈中(事实上,所有局部变量都存储在堆栈中,混淆的是,对于引用类型,变量是引用而不是对象)。静态变量就像'Type'对象的成员,而'Type'不是值类型。 (当然,Java不同,C#实际上并没有一个'型'型,以使其更清晰,每一个'Type'是_different_类型,有不同的成员) – Random832

+6

@ Random832:并不是所有的局部变量是在栈上。关闭本地人不在堆栈上。迭代器块中的本地数据不在堆栈中。异步方法中的本地数据不在堆栈中。注册的当地人不在堆叠上。受到援助的当地人不在筹码上。不要相信当地人会走上前去;这只是错误的。当地人称为当地居民*,因为他们的名字有本地范围*,不是因为他们存储在堆栈中。 –

回答

48

首先,请注意,所有这些都是实现细节。唯一运行的保证是:

  • 当你问一个静态字段,它的存在
  • 使用类型

这几乎是之前的静态构造函数在某点执行它。其他一切都是一个实现细节 - 规范不关心堆栈,堆或其他任何东西。这取决于运行时的实现,并且有效的运行时可以将所有内容放在堆栈上(如果需要的话)或堆上。并且不要忘记寄存器。

现在,让我们来看看一些你已经成功地拿起误解:

  • 静态仅仅是寿命 - 是的。它没有说明什么时候或在哪里存储 - 只要它在您要求时可用。一个兼容的运行时可以自由地使用任何它想要的内存,甚至永远不会加载内存中的字段(例如,将它保存在图像中,这已经在内存中了)
  • 静态会堆积,终生时间 - 很可能,是的。但它不是规范的一部分,并且一个兼容的运行时可以将它存储在任何想要的地方,或者只要适当的保证持续存在。另外,不要忘记,“终身”意味着“至少在AppDomain的生命周期中”;在卸载域时它可能会或可能不会被释放。
  • 静态值类型将堆栈,终生时间 - 最有可能的,没有。同样,一个实现细节,但是堆栈与静态值有意义的语义完全不同。下一点会给你更多的原因:
  • 当assambly被加载时,所有的静态构造函数被调用,包括静态字段。 - 没有这样的要求,没有这样的保证。如果你依靠这个,你的程序将会中断(而且我以前见过很多次)。同样,一个实现细节,但在当前的MSCLR实现中,静态方法倾向于分配在自己的堆中,并且在需要定义类型之前的一段时间。如果你在静态构造函数中抛出一个异常,你可以很容易地看到这个 - 它会导致一个TypeLoadException,很可能在一个首先引用该类型的方法中(不用说,这可以使调试静态技术变得棘手)。
  • 引用类型将堆放在堆上,值类型将放在堆栈上。 - 不会。这会让机制与语义混淆。两者之间的唯一区别是它们的语义 - 其他的一切都取决于实现。如果运行时可以保留堆栈中引用类型的引用语义,那就完全有效。即使使用当前的MSCLR运行时,值类型也始终存储在堆上 - 例如,只要它们是装箱的,或者是引用类型的成员。

有些人可能会混淆。有些人不明白合同和实际实施之间的区别。有些人根本不知道他们在说什么。我希望有一个简单的方法可以知道哪个是哪个,哪个不是。如果有疑问,您可以参考C#/ CLR规范,但这只会告诉您关于合同的事情,而不是实际的事实。

管理内存的整点是,你不应该在乎这些的实现细节。当然,就像任何抽象概念一样,它会泄漏 - 通过各种各样的层和抽象,知道事情的真实情况,CPU微观入侵,内存缓存等是很有意义的。但是这并不意味着依赖于 - 实现可以在任何时候改变,并且它在过去有很多次。

+0

我同意你的观点,它的所有实现细节可以随时间变化。所以我得出结论,AppDomain有它自己的静态堆。它管理值和引用类型的静态成员。 –

+0

@AliAsad是的,基本上。在垃圾回收过程中(它可能引用其他堆中的对象)仍然被考虑,但实际上并未实际收集。另外,请注意字符串是特殊的 - 默认情况下,所有字符串都是被禁止的,所以如果你的静态字符串有一个字符串值,这个值本身可能在另一个堆中(通常是大对象堆)。 – Luaan

+0

@AliAsad至于契约性的东西,不同的应用程序域中的静态必然是孤立的。这不*要求*的对象是在不同的堆,但肯定会使它方便:) – Luaan

1

有一个创建类的实例,所有静态成员都已初始化。

静态类的成员通常存储在堆中,值类型的成员通常存储在堆栈中。

这不一定是这种情况,你可以阅读this博客获取更多信息。

来自C#的语言设计师之一Eric Lippert。

博客显示,与正常知识相反,它不确定值类型是否在堆栈上,引用类型是堆栈中的,但通常是这样。

这只是没有在规范中指定。

5

每当过程在RAM中,我们可以说,内存 大致分为三个区域(在该进程中):栈,堆 和静态(在.NET,实际上是堆内的一个特殊区域 只被称为高频堆)。

静态部分包含“静态”成员变量和方法。什么 完全是静态的?这并不需要创建一个类的实例 这些方法和变量定义为静态

更多here

0

每个静态变量被存储在堆上,无论它是否是一个参考类型或值类型中声明。总共只有一个插槽,无论创建多少个实例。 (这里不需要被创建为虽然存在一个时隙的任何实例。)

此外,当一个静态变量被分配时,它将被存储为方法表的一部分。方法表示当一个类在应用程序中第一次被加载时,单独的内存将被分配在appdomain中用于类内的类变量和方法。 。

如果静态变量是原始类型,它将被存储为方法表的一部分。如果它是引用类型,它将被存储在堆中,并且引用将被存储在Methodtable中。

+0

谢谢。听起来,每个AppDomain都有自己的静态堆。它管理值和引用类型的静态成员。如我错了请纠正我。 –

+1

是的你是绝对正确的 – Lakhtey