目录
现在使用广泛的hotspot虚拟机里,使用opp-klass模型,分两部分来表示Java类和对象,oop也就是ordinary object pointer,普通对象指针,用来标识对象的实例,klass则是用来描述类,标识里面的变量和方法等。oop体系和klass体系中都有instance、method、constantMethod和methodData等结构,大家共同组合起来一起描述Java类的类型,指针,方法,常量池一系列信息,总的来说就是将一个Java类一分为二,每一个oop模型都有一个____OopDesc与其对应,例如上几篇日志讲到常量池模型时,常量池结构对应的oop对象就是constantPoolOop,不同的oop类型有自己特定的结构,klass模型也有___Klass类型与之对应,下面的继承体系中会讲到。
虚函数列表
hotspot虚拟机内部用opp-klass模型来表示一个Java类,oop普通对象指针用来表示类的实例数据,这些数据保存在了指针所存放的内存首地址后那片区域,生成一张数据视图,里面保存了类的实例对象在程序运行期间各个属性的值。klass保存了Java类的各种变量和方法,例如类变量、成员变量、成员方法和构造函数等,它会生成两张表,一张是元数据表,保存了该类的全部数据结构信息,另一张是虚函数表。虚函数是C++里的一个特性,用关键字“virtual”来修饰一个方法,我们知道JVM内部就是使用C/C++实现的,所以里面有很多的C++方法,每一个Java类最后都会被JVM转换成内部的C++类,因为Java语言没有“virtual”这个关键字,也就是没有虚函数,那么Java实现多态的方式是什么?答案是Java类将所有的函数都当作“虚函数”来处理,不用在前面添加“virtual”之类的关键字修饰,类中的所有方法都可以直接被子类覆盖,所以JVM的klass模型为每一个类都生成虚函数列表,用以维护Java类和内部C++类的对应关系。
opp、klass和handle类型
说到JVM内部如何描述一个Java类,除了opp-klass体系中的oop类和klass类外,其实还有一个handle类同样用来标识一个Java类的信息。klass类描述了Java类中的元数据和虚函数表,这些信息的实际数据保存在了oop实例里面,handle类是对oop的封装,handle类里有专门的指针指向oop实例,oop实例里又有指针指向klass,所以如果一个Java方法被调用,那么一定是先通过handle里面的指针获得oop实例,再通过oop实例来获得klass,klass中包含有方法执行的接口,能够执行Java类方法。
oop体系
oop前面说了就是普通对象指针,存放Java类的实际数据,JVM内部定义了许多___oopDesc*类型的指针,这些指针都由GC(garbage collection垃圾回收)管理,这里的oop指针前面要加上“普通”两个字,原因是区分开与SmallTalk语言中的“直接指针”,所谓直接指针就是将实际数据直接存放到指针变量里,并不会在GC堆上分配,这样对象在离开了函数的作用域后就会直接从对阵上被释放,而无需GC区回收。普通对象指针由于GC堆管理,垃圾回收需要GC来处理。来看看oop体系里面都有哪些类型变量:
//所有oops共同基类
typedef class oopDesc* oop;
//表示Java类型实例
typedef class instanceOopDesc* instanceOop;
//表示Java方法
typedef class methodOopDesc* methodOop;
//表示Java方法中的只读信息
typedef class constMethodOopDesc* constMethodOop;
//性能统计数据结构
typedef class methodDataOopDesc* methodDataOop;
//oops数组
typedef class arrayOopDesc* arrayOop;
//引用类型数组
typedef class objArrayOopDesc* objArrayOop;
//基本类型数组
typedef class typeArrayOopDesc* typeArrayOop;
//.class文件中的常量池
typedef class constantPoolOopDesc* constantPoolOop;
//常量池缓存
typedef class constantPoolCacheOopDesc* constantPoolCacheOop;
//指向klass实例
typedef class klassOopDesc* klassOop;
//对象头
typedef class markOopDesc* markOop;
typedef class compliedICholderOopDesc* compliedICHolderOop;
上图所示就是整个oop体系的结构,每一个类型代表JVM内部一个特定的对象模型,也就是说在Java程序运行时,每创建一个新的对象,都会有对应的OopDesc对象生成。
klass体系
到klass,上面说过klass是JVM内部建立的Java类的对等模型,里面有Java类中的类变量,成员变量、成员方法和继承信息等,正是因为JVM在内部建立了一个和Java类对等的C++类模型,才能在程序运行过程动态反射出类的全部信息。除此之外klass模型还提供了虚函数列表,里面包含了一些函数的调用接口,在访问Java类时是通过handle类获得oop实例,在通过oop实例里的指向klass的指针得到klass实例,来完成函数调用。来看看klass类型的体系结构:
//所有klass共同基类
class Klass;
//JVM内部与Java类对等的结构
class InstanceKlass;
//描述java.lang.Class实例
class InstanceMirrorKlass;
//描述java.lang.ref.Reference子类
class InstanceRefKlass;
//Java类方法
class methodKlass;
//Java类方法对应的字节码指令
class constMethodKlass;
class methodDataKlass;
//klass链路的末端
class klassKlass;
class instanceKlass;
//标识Java数组
class arrayKlass;
//标识引用类型数组
class objArrayKlass;
//标识基本类型数组
class typeArrayKlass;
//.class文件中的常量池
class constantPoolKlass;
//常量池缓存
class constantPoolCacheKlass;
class compliedICHolderKlass;
这里有一点要注意的是,klass类型的基类虽然是Klass,但Klass不是最顶级父类,它还继承了klass_vtbl类,这才是最顶级父类。
handle类
最后一个,handle类,它的作用是对oop和klass进行封装,该类为oop和klass提供了两套不同的封装方式,对于一个oop类型,它存储的是Java类的实际数据,相当于标识了一个C++类型里的属性,klass在JVM里也会被封装成oop类型,当一个类的生命周期结束后,GC便会去回收,除了回收实际数据oop外,包含了元数据和虚函数表的klass也会被回收,那么为什么要把klass也封装成oop?前面说过oop普通对象指针区别于直接指针的一个特性是,直接指针因为数据直接存储在指针变量里,所以当离开了作用域后,它会立刻被从堆栈中释放掉,普通指针则需要通过GC来回收,把klass封装成oop,原因也就是为了更方便地被GC回收。最终,handle通过内部的oop实例,获得klass,相当于直接封装了oop,间接封装了klass。