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

28

在思考Java/C#编程时,我想知道属于对象的方法在内存中是如何表示的,并且这个事实与多线程有什么关系。

  1. 每个对象在内存中都单独实例化一个方法吗?还是所有相同类型的对象共享一个方法实例?
  2. 如果是后者,执行线程如何知道使用哪个对象的属性?
  3. 是否可以使用反射修改C#方法的代码,针对一种类型的多个对象中的一个对象进行修改?
  4. 一个不使用类属性的静态方法是否总是线程安全的?

我试着想清楚这些问题,但我对它们的答案非常不确定。

3个回答

26

你的源代码中的每个方法(在Java、C#、C++、Pascal等面向对象和过程式编程语言中)在二进制文件和内存中只有一个副本。

一个对象的多个实例具有不同的字段,但是它们都共享相同的方法代码。技术上,存在一个过程,它会接受一个隐藏的this参数,以提供对对象执行方法的幻觉。实际上,您调用的是一个过程,并将一个结构体(一组字段)与其他参数一起传递给它。以下是一个简单的Java对象和更或多或少等效的伪C代码:

class Foo {
  private int x;

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

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

被翻译成了这个伪代码(封装是由编译器和运行时/虚拟机强制实现的):

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是一个参数,所以它是线程局部的,因此每个线程可以同时操作不同的对象,即使它们运行相同的代码。
话虽如此,你不能只修改一个实例的方法,因为方法的代码是共享的,适用于所有实例。

7
Java规范并没有规定如何进行内存布局,不同的实现可以随心所欲地进行,只要在相关方面符合规范即可。
话虽如此,主流的Oracle JVM(HotSpot)使用了普通对象指针(oops)。这些oopp由两个头部单词组成,后跟数据,这些数据包含了实例成员字段(对于基本类型,这些存储为内联,对于引用类型则存储为指针)。
两个头部单词之一是类单词,它是指向klassOop的指针。这是一种特殊的oop类型,其中包含了该类的实例方法的指针(基本上,这是Java类型相应的C++ vtable)。klassOop是VM级别的Class对象表示。
如果你对低级细节感到好奇,可以通过查看OpenJDK源代码中某些oop类型的定义来了解更多信息(klassOop是一个很好的起点)。
简而言之,Java为每种类型的每个方法保留一个代码块。代码块在每个类型的每个实例之间共享,并使用隐藏的this指针来确定使用哪个实例的成员。

谢谢您的解释,我觉得了解JVM内部发生的事情非常有趣。+1 - Emiswelt

4

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

  • 虚方法
  • 非虚方法
  • 静态方法

当您的代码被执行时,堆上基本上有两种对象。

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

回答您的问题:

  1. 每个对象在内存中都实例化了一个方法,还是所有相同类型的对象共享一个方法实例?

这是一种错误的理解对象的方式。所有方法都只属于类型。可以这样看待它。方法只是一组指令。第一次调用特定的方法时,IL代码会被JIT编译成本地指令并保存在内存中。下一次调用时,地址从方法表中获取,并再次执行相同的指令。

2.如果是后者,执行线程如何知道使用哪个对象的属性? 每次在类型上调用静态方法时,都会查找相应类型对象的方法表,并找到JIT指令的地址。对于非静态方法,调用该方法的相关对象会维护在线程的本地堆栈上。基本上,您可以获得堆栈上最近的对象。那就是我们想要调用方法的对象。

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


非常有趣的阅读。它很好地回答了我关于C#的问题。加一。 :) - Emiswelt
我不理解"静态方法"是如何保证线程安全的。这就是为什么我没有回答最后一个问题的原因。那个术语"线程安全"是什么意思? - gprasant
我只是想知道是否可以安全地同时从多个线程调用该方法。 - Emiswelt

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