内存中的方法表示是什么?

时间:2011-11-20 20:47:05

标签: c# java memory jvm clr

在思考Java / C#中的编程时,我想知道如何在内存中表示属于对象的方法,以及这个事实如何涉及多线程。

  1. 是一个单独为内存中的每个对象实例化的方法 所有相同类型的对象共享一个方法实例?
  2. 如果是后者,执行线程如何知道哪个对象 要使用的属性?
  3. 是否可以修改方法的代码 C#用一个反射,只有一个对象的多个对象 相同的类型?
  4. 一个不使用类属性的静态方法总是线程安全吗?
  5. 我试着对这些问题下定决心,但我对他们的答案非常不确定。

3 个答案:

答案 0 :(得分:17)

源代码中的每个方法(使用Java,C#,C ++,Pascal,我认为每个OO和过程语言......)在二进制文件和内存中只有一个副本。

一个对象的多个实例具有单独的字段,但它们共享相同的方法代码。从技术上讲,有一个过程采用隐藏的this参数来提供对对象执行方法的错觉。实际上,您正在调用一个过程并将结构(一个字段)与其他参数一起传递给它。这是一个简单的Java对象和或多或少等效的伪C代码:

class Foo {
  private int x;

  int mulBy(int y) {
    return x * y
  }
}

Foo foo = new Foo()
foo.mulBy(3)

转换为此pseude-C代码(封装由编译器和运行时/ VM强制执行):

struct Foo {
    int x = 0;
}

int Foo_mulBy(Foo *this, int y) {
    return this->x * y;
}

Foo* foo = new Foo();
Foo_mulBy(foo, 3)

您必须在代码和局部变量及其操作的参数(数据)之间划出差异。数据存储在每个线程本地的调用堆栈中。代码可以由多个线程执行,每个线程都有自己的指令指针副本(放在当前执行的方法中)。另外因为this是一个参数,它是线程本地的,所以每个线程可以同时在不同的对象上运行,即使它运行相同的代码。

话虽如此,你不能只修改一个实例的方法,因为方法代码在所有实例之间共享。

答案 1 :(得分:4)

我将尝试在C#的上下文中回答这个问题。基本上有3种不同类型的方法

  • 虚拟
  • 非虚拟
  • 静态

执行代码时,基本上有两种在堆上形成的对象。

  • 与对象类型对应的对象。这称为类型对象。这包含类型对象指针,同步块索引,静态字段和方法表。
  • 对象本身对应的对象,包含所有非静态字段。

回答你的问题,

  1. 是一个单独为内存中的每个对象实例化的方法,还是同一类型的所有对象共享该方法的一个实例?
  2. 这是理解对象的错误方法。 所有方法仅按类型 。以这种方式看待它。方法只是一组指令。第一次调用特定方法时,IL代码被JIT到本机指令中并保存在内存中。下次调用此地址时,将从方法表中获取地址,并再次执行相同的指令。

    2. 如果是后者,执行线程如何知道要使用哪个对象的属性?   对Type进行的每个静态方法调用都会导致从相应的Type对象中查找方法表并查找JITed指令的地址。如果方法不是静态的,则在线程的本地堆栈上维护调用该方法的相关对象。基本上,您可以获得堆栈中最近的对象。那就是 总是 我们想要调用该方法的对象。

    3. 是否可以在C#中使用反射修改方法的代码,并且只修改同一类型的多个对象中的一个对象?  不,现在不可能。 (而且我很感激)。原因是反射只允许代码检查。如果你弄清楚某些方法实际意味着什么,你将无法在同一个程序集中更改代码。

答案 2 :(得分:3)

Java规范没有规定如何进行内存布局,不同的实现可以做任何他们喜欢的事情,只要它符合重要的规范。

话虽如此,主流Oracle JVM(HotSpot)的工作原理是oops - 普通对象指针。它们包含两个单词标题,后跟数据,其中包含实例成员字段(原始类型的内联存储,以及引用成员字段的指针)。

两个标题词中的一个 - 类词 - 是指向klassOop的指针。这是一种特殊类型的oop,它包含指向类的实例方法的指针(基本上是Java等价的C ++ vtable)。 klassOop类似于与Java类型对应的Class对象的VM级表示。

如果您对低级细节感到好奇,可以通过查看OpenJDK源代码来了解一些oop类型的定义(klassOop是一个很好的起点)。

tl; dr Java为每种类型的每种方法保存一个代码块。代码块在每个类型的实例之间共享,隐藏此指针用于知道要使用的实例成员。