从Oop-Klass模型看透反射

  《红楼梦》第十二回,贾瑞因痴迷王熙凤,被王熙凤折腾的眼看就快不行了。当然这里面是没有多少爱的,完全因王熙凤的美貌而起。就在这时来了一个跛足道人,带来了一面宝镜,说能治好贾瑞的病。当然这可不是一面普通的镜子,而是由警幻仙子做制。并且道人还告诫贾瑞,这面镜子专治邪思妄动之症,有济世保生之功。所以带他到世上,单与那些聪明俊杰,风雅王孙等看照。千万不可照正面,只照他的背面。

  贾瑞先是依着跛足道人的话,照的背面。可是却看到一个骷髅立在里面,把贾瑞吓了个半死。这时贾瑞以为道士骗他,赶忙照了正面,这下看到了美貌的王熙凤,贾瑞满意极了。可是他的命却没有了。

  正面的那个骷髅,正是病入膏肓的贾瑞自己,可是他却不敢相信那就是他自己现在的形象。他宁愿相信假的,也不愿相信真的。跛足道人明明叫贾瑞照背面,可是贾瑞却偏偏喜欢照正面。那是因为正面有贾瑞喜欢的东西。这时,再高明的医生也救不了他了。可想曹雪芹写到这里,虽说用的是风趣幽默的语言,从中却能看出他的无奈。

  每天清晨傍晚,当我们站在镜子时前可曾想过,摘掉喜好的有色眼镜打开自己的心肺肠肝,镜中是否还是自己当初想要的样子。

  以铜为镜正衣冠、以史为镜知兴替、以人为镜明得失。人类需要镜子去看清自己、看清时间、看清取舍,同样的是 类也需要。

  JAVA是人造语言,其中的很多机制和抽象的出现都是源于人类社会的运作方式和道理。我们要像理解自己周围的世界一样去理解语言背后的道理,就像语言的创造者用对周边世界的理解去创造这门语言时一样。

  反射是学习JAVA的过程中无法绕过的大山,反射的使用容易、但理解很难,因为反射涉及到了JVM的数据结构和类加载机制等底层机制。我们从字面意思来理解一下反射,那就是靠一面“镜子”,去看清类的内部构造。

  在使用时我们发现,我们通过操作一个类的java.lang.class对象,可以在运行时窥探这个类的成员、方法、注解等,事实上反射的定义也是这样:

  在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。

  想要弄清楚反射,我们不得不提的是JVM的OOP-KLASS模型(源码在openjdk-8/openjdk/hotspot/src/share/vm/oops内)。

  这里的 oop 指的是 普通对象指针 (普通对象指针),它表示对象的实例信息,看起来像个指针实际上是藏在指针里的对象。而klass 则包含元数据和方法信息 ,用来描述Java类。

  那么为何要设计这样一个一分为二的对象模型呢?这是因为HotSopt JVM的设计者不想让每个对象中都包含一个vtable(虚函数表),所以就把对象模型拆成klass和oop,其中oop中不包含任何虚函数,而klass就包含虚函数表,可以进行方法调度。这个模型其实是 参照的Strongtalk VM的对象模型。

  klass
  一个Klass对象代表一个类的元数据。它提供:

  language level class object (method dictionary etc.)
  provide vm dispatch behavior for the object
  所有的函数都被整合到一个C++类中。

  Klass对象的继承关系: xxxKlass <:< Klass <:< Metadata <:< MetaspaceObj

  可以说Klass就是类在JVM中的数据结构。

  oop

  oop 类型其实是 oopDesc* 。在Java程序运行的过程中,每创建一个新的对象,在JVM内部就会相应地创建一个对应类型的oop对象。各种oop类的共同基类为 oopDesc 类。JVM内部,一个Java对象在内存中的布局可以连续分成两部分: instanceOopDesc 和实例数据。 instanceOopDesc 和 arrayOopDesc 又称为对象头。instanceOopDesc 对象头包含两部分信息: Mark Word 和 元数据指针 ( Klass* ):

  Mark Word :instanceOopDesc中的 _mark 成员。它用于存储对象的运行时记录信息,如哈希值、GC分代年龄(Age)、锁状态标志(偏向锁、轻量级锁、重量级锁)、线程持有的锁、偏向线程ID、偏向时间戳等。Mark Word允许压缩。
  元数据指针 :instanceOopDesc中的 _metadata 成员,它是联合体,可以表示未压缩的Klass指针( _klass )和压缩的Klass指针。对应的klass指针指向一个存储类的元数据的Klass对象
  可以说,instanceOopDesc就是普通对象的对象头,其中有一个指针指向了它所属的Klass,也就是类结构。

  JVM在加载一个类时,会在方法区创建该类的Klass对象,并同时生成一个属于该Klass对象的java.lang.class对象放于堆中,也就是java.lang.class的instanceOopDesc。当我们访问类的元数据时,是通过java.lang.class的instanceOopDesc来访问的,java.lang.class的instanceOopDesc中包含了访问该类Klass对象的属性、方法、注解等元数据的方法。

  它们的指向是这样的:类A的instanceOopDesc  ---->  类A的Klass  ----->  类A的java.lang.class的instanceOopDesc。

  也就是说Klass代表的类的元数据是不会直接暴露给对象进行访问的,而是通过Klass的java.lang.class对象来进行间接的访问。这样做可以规范外部对Klass访问的行为,防止对元数据造成破坏。

  Klass持有一个指向对应java.lang.class对象的引用,叫做_java_mirrojava.lang.class对象被设计为类的元数据的一面镜子,这也是反射机制命名的原因。

 

posted @ 2019-11-21 21:33  牛有肉  阅读(740)  评论(0编辑  收藏  举报