【问题标题】:What's the method representation in memory?内存中的方法表示是什么?
【发布时间】:2011-11-20 20:47:05
【问题描述】:

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

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

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

【问题讨论】:

    标签: c# java memory jvm clr


    【解决方案1】:

    源代码中的每个方法(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是一个参数,它是线程局部的,所以每个线程可以同时操作不同的对象,即使它运行相同的代码。

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

    【讨论】:

      【解决方案2】:

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

      话虽如此,主流的 Oracle JVM (HotSpot) 使用称为 oops - 普通对象指针的东西工作。它们由两个字头组成,后跟包含实例成员字段的数据(存储在原始类型的内联中,并作为引用成员字段的指针)。

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

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

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

      【讨论】:

      • 感谢您的解释,我发现了解 JVM 内部发生了什么真的很有趣。 +1
      【解决方案3】:

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

      • 虚拟
      • 非虚拟
      • 静态

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

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

      针对您的问题,

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

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

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

      3.是否可以修改C#中方法的代码,对一个对象进行反射,并且只对许多相同类型的对象中的一个对象进行反射? 不,现在不可能。 (我对此表示感谢)。原因是反射只允许代码检查。如果您弄清楚某些方法的实际含义,那么您将无法更改同一程序集中的代码。

      【讨论】:

      • 读起来真的很有趣。它很好地涵盖了我与 C# 相关的问题。 +1。 :)
      • 我不明白线程安全的“静态方法”是什么意思。这就是为什么我没有回答最后一部分? “线程安全”这个术语是什么意思?
      • 我只是想知道从多个线程同时调用该方法是否安全。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-01-26
      • 1970-01-01
      • 1970-01-01
      • 2011-04-04
      • 2011-01-25
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多