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

2022-09-01 14:30:45

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

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

我试图对这些问题下定决心,但我对它们的答案非常不确定。


答案 1

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

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

class Foo {
  private int x;

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

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

被转换为以下伪 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

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


答案 2

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

话虽如此,主流的Oracle JVM(HotSpot)基于称为oops的东西 - 普通对象指针。它们由两个标头单词后跟数据组成,后者包含实例成员字段(用于基元类型以内联方式存储,并作为引用成员字段的指针)。

两个标题词之一 - 类词 - 是指向 klassOop 的指针。这是一种特殊类型的oop,它保存指向类的实例方法的指针(基本上,Java相当于C++ vtable)。klassOop 是与 Java 类型对应的 Class 对象的 VM 级表示形式。

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

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