一直对实例方法的内存如何分配有很大疑惑(通俗的讲就是实例方法在内存中有几份),找了很多资料,以下是一个比较令我满意的答案,记录下:
方法 (Method) 是一种类型定义,所以,它被存放在 Type Object 上,Type Object 是一个被分配在托管堆上的特殊类型,在同一个 AppDomain 中,每一个类型,都对应一个全局的 Type Object。每个引用类型的实例,都包含一个指向它的直接类型的 Type Object 的指针,每个 Type Object 也存在类似的指针,用来标识它的直接父类型的 Type Object。
当调用静态方法时,CLR 会根据方法调用去寻找其对应的 Type Object,然后,把方法 JIT,JIT 之后的方法是本机代码,可以直接运行,然后,这部分代码被加载进入内存,方法的参数被加载进入当前执行栈,原来的执行上下文地址也被记录到执行栈;方法开始执行,执行完后,执行栈中的返回地址被读出,然后 CLR 利用本机跳转指令,跳转到该返回至继续执行。
当调用实例方法时,CLR 会根据实例的 Type Object 指针找到对应的 Type Object,然后,把方法 JIT,JIT 之后的方法是本机代码,可以直接运行,然后,这部分代码被加载进入内存,该实例对象,以及方法的参数被加载进入当前执行栈 (实例对象永远是第一个参数,即 arg0,利用 ldarg0 指令进行读取),原来的执行上下文地址也被记录到执行栈;方法开始执行,执行完后,执行栈中的返回地址被读出,然后 CLR 利用本机跳转指令,跳转到该返回至继续执行。
如果方法已经被 JIT 过,则不会被第二次 JIT。
方法在 IL 中是以字节流的形式存在的,所以,它仍然会占据内存。
方法 JIT 之后会被驻留在该进程的地址空间里面,因此,它也会在运行时占据内存。
方法的元数据存放在程序集 MethodRef 以及 MethodDef 表中。
定义在值类型上的实例方法就比较麻烦了,大家有兴趣可以想想它怎么执行的。因为值类型没有 Type Object 指针。
如果值类型实现一个接口,在执行接口的方法实现的时候就更加麻烦了,大家也可以想想,欢迎讨论!
最后,
大家都以为“ 静态方法在堆上分配内 存,实例方法在堆栈上”
这句话完全不靠谱,不要被迷惑了。。。只要提到方法,它就一定在 Type Object 上,也就是被分配在托管堆上。
总结下,不管是实例还是静态方法,被JIT只存在一份,不过实例方法多了一个引用该实例的参数作为它的第一个参数!