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

本文关键字:表示 是什么 内存 方法 | 更新日期: 2023-09-27 18:15:19

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

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

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

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

每个方法在你的源代码(在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)

被翻译成以下伪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是一个参数,所以它是线程本地的,所以每个线程可以并发地操作不同的对象,即使它们运行相同的代码。

也就是说,您不能仅修改一个实例的方法,因为该方法代码在所有实例之间共享。

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

话虽如此,主流的Oracle JVM (HotSpot)是基于oops——普通对象指针来工作的。

两个头字中的一个——类字——是指向类对象的指针。这是一种特殊类型的oop,它包含指向类的实例方法的指针(基本上,Java相当于c++的虚函数表)。klassOop是对应于Java类型的Class对象的一种vm级表示。

如果你对底层的细节感兴趣,你可以在OpenJDK源码中找到更多的oop类型的定义(klassOop是一个很好的起点)。

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

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

    虚拟
  • 非虚拟
  • 静态

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

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

关于你的问题,

  1. 方法是为内存中的每个对象单独实例化还是相同类型的所有对象共享该方法的一个实例?

这是一个错误的理解对象的方式。所有的方法都是类型,只有。这样看。方法就是一组指令。第一次调用特定方法时,将IL代码编译为本机指令并保存在内存中。下一次调用该方法时,将从方法表中获取该地址,并再次执行相同的指令。

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

3。是否有可能在c#中修改一个方法的代码,并且只有一个相同类型的许多对象中的一个对象的反射?不,现在不可能。(对此我很感激)。原因是反射只允许代码检查。如果您弄清楚某些方法的实际含义,则无法更改同一程序集中的代码。