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

时间:2013-02-08 21:58:57

标签: c# java php oop static-members

假设你有一个班级,

class Foo
{
    public static bar;
}

当你说:

new Foo();

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

......当你再说一次时:

new Foo(); 

......现在你有另一个可用于该物体的空间。

然而,静态场的确切位置在哪里?

我真正想要学习的是:

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

11 个答案:

答案 0 :(得分:101)

虽然类型系统的具体细节是依赖于实现的,但是让我更详细地介绍一下它取决于你不应该关心。我将根据书籍CLR via C# by Jeffrey Richter和文章See How the CLR Creates Runtime Objects by Hanu Kommalapati et al.original MSDN May 2005 issue)描述它在Microsoft的实现(.NET)中是如何工作的。


假设你有一个班级:

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 index和{ {1}})。类型句柄是指向Type handle对象的指针,该对象描述实例的类型,在本例中为类型的Foo

再次说Type时,会分配新空间,该空间将再次包含该类型实例字段的空间。如您所见,实例字段与对象实例相关联。

运行时将每个实例字段放在距对象数据开头的固定偏移处。例如,new Foo()可能位于抵消+4。实例字段的地址只是对象的地址加上字段的偏移量。

静态字段在哪里生效?

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

运行时加载类型时会创建Type对象 1 。此结构包含运行时能够分配新实例,调用方法和执行转换等所需的各种信息。它还包含静态字段的空间,在本例中为Typebar

运行时将每个静态字段放置在距类型数据开头的某个偏移处。每种类型都有所不同。例如,num可能位于偏移+64。静态字段的地址是bar对象的地址加上字段的偏移量。这种类型是静态的。

Displays some object structures, and their relationships.

1 )在Microsoft .NET中,多个不同的结构描述了一种类型,例如 MethodTable EEClass 结构。

答案 1 :(得分:16)

这完全取决于有问题的实施。对于C#和Java,允许运行时确定存储变量内存的位置。对于C和大多数编译语言,编译器都会做出此决定。

话虽如此,在实践中,并不重要。它由规范确定的用法,因此您可以自由地使用知道行为的变量。

答案 2 :(得分:5)

这种语言因语言而异,甚至可能因平台而异:...

例如,在.NET端,静态成员是"关联的"使用管理EEClass定义,其中可以是一个堆分配的 OR a"无论在哪里"已分配的成员(C#规范没有指定堆/堆栈行为,它是VM的实现细节)

答案 3 :(得分:5)

可能有例外,但对于引用类型,new - 关键字通常在名为“heap”的内部数据结构中创建一个对象。堆由CLR(公共语言运行时)管理。无论您是静态成员还是实例成员还是局部变量都没有区别。

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

它只是静态的参考;此区别不适用于引用的对象(除非该对象是值类型)。静态成员,实例成员和局部变量都可以引用同一个对象。

答案 4 :(得分:5)

对于Java,静态字段引用的对象将像其他对象一样驻留在on the heap

  

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

该字段将在the class is loaded时初始化(如果声明包含初始化),该字段在第一次出现以下任何一个之前发生:

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

通过2个特殊的JVM指令getstaticputstatic来访问静态字段。但除了这种区别外,静态字段与非静态字段类似。

答案 5 :(得分:4)

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

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

答案 6 :(得分:2)

静态成员和常量存储在堆上。与堆上可以进行垃圾收集的对象不同,静态成员和常量一直保持到Appdomain被拆除为止,因此曾经应该小心处理静态字段

答案 7 :(得分:1)

静态变量属于类而非对象,因此即使初始化数千个bar时刻,内存中也只有一个Foo

答案 8 :(得分:1)

这取决于语言的语言或语言的设计者。如果我谈论Java,静态成员存储在JVM的Method区域中,并且所有对象都链接到它们。还有一件事非常重要,我们可以访问静态数据成员而无需创建类的对象。这意味着将内存分配给静态数据成员不依赖于对象的创建。类。

答案 9 :(得分:1)

按规范,静态变量存储在Constant Pool中。 JVM将此信息存储在Permanent Generation中。

答案 10 :(得分:0)

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

所以基本上它就像

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

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

来自wikipedia

    “数据区包含程序使用的全局和静态变量     用值显式初始化。“