本人在《Java虚拟机对象探秘》一文提到过Class & Klass & KlassKlass的概念,这里将基于那篇文章的内容对Java反射进行一个简单的实现。
理论基础
理论基础来源于RednaxelaFX的解释:
每个Java对象的对象头里,_klass字段会指向一个VM内部用来记录类的元数据用的InstanceKlass对象;InsanceKlass里有个_java_mirror字段,指向该类所对应的Java镜像——java.lang.Class实例。HotSpot VM会给Class对象注入一个隐藏字段“klass”,用于指回到其对应的InstanceKlass对象。这样,klass与mirror之间就有双向引用,可以来回导航。java.lang.Class实例并不负责记录真正的类元数据,而只是对VM内部的InstanceKlass对象的一个包装供Java的反射访问用。
java.lang.Class对象概要
在当前的实现中,Class就相当于InstanceKlass,Java API层面的“对象”都是以生成Instance对象的方式开辟内存空间,java.lang.Class也不例外,用一个Instance_对象简单表示之。
接下来看一个例子:
|
|
反编译后结果:
|
|
从这个例子可以看出,基本类型和引用类型通过.class
获得java.lang.Class对象的方式是不一样的,基本类型会调用包装类的静态字段TYPE,而引用类型会调用ldc指令。
在InstanceKlass中添加代码`private Instance javamirror;`,它表示在JVM中java.lang.Class的实例,添加get、set方法。
引用类型的java.lang.Class对象
这里解决引用类型的java.lang.Class对象问题,修改将在ClassLoader_以及LDC类中进行。
所有Java对象的类对象都是java.lang.Class产生的对象,像一般对象地,先产生InstanceKlass对象,再通过Instance对象开辟内存空间,不同的是,“HotSpot VM会给Class对象注入一个隐藏字段‘klass’”。
1. 初步改造ClassLoader_的构造器
代码如下:
|
|
运行程序,java.lang.Class的InstanceKlass_对象就在生成类加载器时生成好了:
2. InstanceKlass_添加属性用来表示Java镜像
这一步就是简单的在代码中添加javamirror属性:
|
|
3. 在Instance_注入隐藏属性klass
|
|
4. 实现instanceKlass_与mirror之间双向引用
这一步主要是对ClassLoader_中的loadClass进行修改:
|
|
5. 修改LDC,将类对象推入操作数栈
添加代码:
|
|
通过以上步骤,完成了Java反射之于引用类型的准备工作。
完成部分反射功能
这里实现最简单的反射功能,看例子:
|
|
通过.class和getClass()来获得ClassTest的java.lang.Class类对象,然后调用类对象的getName方法打印出ClassTest类名。
1. 修改JObject
在OpenJDK中拥有单独的方法Java_java_lang_Object_getClass(),这里为了简便,直接用之前已经写好的Java_java_lang_Object_registerNatives()来注册,需要强调的是getClass()是Object的native方法,所以注册是通过JObject的方法来实现。添加的代码如下:
|
|
2. 在JVM_ENTRY实现相应的native方法
这里的native方法指的是GetObjectClass()以及JVM_GetClassName(),前者用来实现getClass的逻辑,后者则用于实现getName。代码如下:
|
|
以上代码发挥了klass与mirror之间双向引用的作用。
3. 在java.lang中添加JClass
这个类的实现类似于JObject,也采用了一个注册方式来调用native方法,但是和JObject不同的是,编译出来的字节码文件没有主动调用该类registerNatives方法的指令(解决这个问题见下一步)。为了方便查阅这里贴出完整类代码:
|
|
4. 修改INVOKE_NATIVE
前面提到了javac编译出来的字节码文件没有主动调用该JClass类registerNatives方法的指令,所以需要给它一个运行的时机,这里将这个“时机”放在INVOKE_NATIVE中,即添加代码:
|
|
这样当运行INVOKE_NATIVE实例时就会完成JClass中本地方法的注册。
以上就实现了最基本的反射功能,结果是打印出两行ClassTest。
小结
理解Java反射的实现原理在于理解记录类元数据的对象和java.lang.Class实例的关系,它们是双向引用的。