静态字段究竟如何在内部工作?

假如你有一堂课,

class Foo { public static bar; } 

当你说:

 new Foo(); 

我可以想象,在内存中,为这个对象保留一个空间。

…当你再说一遍:

 new Foo(); 

…现在你有另一个空间可用的对象。

但是,静态字段究竟在哪里?

我真正想学的是:

对象的引用如何引用它们引用的对象的同一个字段?

虽然types系统的确切细节是依赖于实现的,但让我进入一些更详细的内容,而不仅仅是说它取决于,而且你不应该在意 。 我将根据Jeffrey Richter撰写的C#本书以及文章“ 查看CLR如何创build运行时对象”(由Hanu Kommalapati等人撰写 )来描述它在Microsoft的实现(.NET)中是如何工作的 。 ( 原始MSDN 2005年5月号 )。


假设你有一堂课:

 class Foo { // Instance fields string myBar = "Foobar"; int myNum; // Static fields static string bar = "Foobar"; static int num; } Foo myFoo = new Foo(); Type typeOfFoo = typeof(Foo); 

实例字段在哪里?

每当你说new Foo() ,空间被分配和初始化的对象实例,并调用构造函数。 这个实例在下图中显示为Foo的实例 。 比如实例只包含类的实例字段(在本例中为myBarmyNum ),而在堆中分配的对象有两个由运行时使用的额外字段( Sync block indexType handle )。 types句柄是指向描述实例types的Type对象的指针,在这种情况下, types为Foo

当再次说new Foo()时,会分配新的空间,这将再次包含types实例字段的空间。 如您所见,实例字段与对象实例相关联。

运行时将每个实例字段放置在对象数据开始的固定偏移处。 例如, myBar可能生活在偏移+4。 实例字段的地址就是对象的地址加上字段的偏移量。

静态字段在哪里生活?

C#和Java中的静态字段不与任何对象实例关联,但与types关联。 类,结构和枚举是types的例子。 只有一次(每种types)是分配一些空间来保存静态字段的值。 为描述types的Type结构中的静态字段分配空间是有意义的,因为每个types也只有一个Type对象。 这是C#和Java所采取的方法。

Type对象1是在运行时加载types时创build的。 这个结构包含了运行库能够分配新实例,调用方法和执行转换等所需的各种信息。 它还包含静态字段的空间,在这种情况下, barnum

运行时已经把每个静态字段放在types数据的起始偏移处。 每种types都是不同的。 例如, bar可能住在偏移+64。 静态字段的地址是Type对象的地址加上字段的偏移量。 types是静态已知的。

显示一些对象结构及其关系。

1 )在Microsoft .NET中,多个不同的结构描述一个types,比如MethodTableEEClass结构。

这完全取决于有关的实施。 对于C#和Java,允许运行时确定在哪里存储variables的内存。 对于C语言和大多数编译语言,编译器会做出这个决定。

这就是说,在实践中, 这并不重要 。 由规范确定的用法,所以你可以自由地使用知道行为的variables将得到保证。

从语言到语言,这个变化很大,甚至可以从平台到平台大幅变化。

例如,在.NET方面,静态成员与控制的EEClass定义“关联”,这个定义可以是堆分配的,也可以是“任何地方”分配的成员(C#规范没有指定堆/堆栈行为,它是VM的实现细节)

对于Java,由静态字段引用的对象将像其他对象一样驻留在堆上 :

堆是从中分配所有类实例和数组的内存的运行时数据区。

该字段将被初始化(如果该声明包含一个初始化),当该类被加载 ,这是在第一次出现任何一个之前发生的:

  • 该类的一个实例被创build。
  • 一个由该类声明的静态方法被调用。
  • 分配一个由类声明的静态字段。
  • 使用由类声明的静态字段,该字段不是常量variables(§4.12.4)。

对静态字段的访问是通过2个特殊的JVM指令完成的, getstatic和putstatic 。 但除此之外,静态字段与非静态字段类似。

可能有例外,但对于引用types, new关键字通常在名为“heap”的内部数据结构中创build一个对象。 该堆由CLR(公共语言运行时)pipe理。 不pipe你有静态或实例成员还是局部variables,都没有区别。

静态成员和实例成员(没有关键字static成员)之间的区别在于,静态成员每个types(类,结构)只存在一次,实例成员每个实例(每个对象)只存在一次。

只是静态的参考, 这个区别不适用于被引用的对象(除非该对象是一个值types)。 一个静态成员,一个实例成员和一个局部variables都可以引用同一个对象。

我只熟悉C#,这是我对它的理解:

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

静态成员和常量存储在堆上。 与可以获取垃圾回收的堆上的对象不同,静态成员和常量一直保留,直到AppDomain被拆除,因此,一旦处理静态字段时应该小心

这取决于语言的语言或语言的devise者。 如果我谈论Java,静态成员存储在JVM的“方法”区域中,并且所有对象都链接到它们。 还有一点需要了解的是,我们可以在不创build类的对象的情况下访问静态数据成员。这意味着内存分配给静态数据成员不依赖于创build对象的那个类。

通过指定静态variables存储在常量池中 。 JVM将这些信息存储在Permanent Generation中。

静态variables属于类而不是对象,因此即使初始化了数千个Foo的瞬间,内存中也只有一个bar

通常,静态variables存储在程序存储器的数据段中。 因此对于每个正在运行的程序中创build的类将在数据段上创build静态variables,并且在代码段中初始化所有其他variables。

所以基本上就是这样

 +++++++++++++++++++++++++++++++++++++ + DATA Segment + -static vars + +---------------------------------- + instances | instances | instances| + | | | 

这里单个区域在实例之间共享。

来自wikipedia “数据区包含程序使用的全局variables和静态variables
显式初始化一个值“。