Java反射在JVM中的实现 深碍√TFBOYSˉ_ 2022-07-16 09:59 137阅读 0赞 ## 什么是反射? ## 反射使程序代码能够接入装载到JVM中的类的内部信息,允许在编写与执行时,而不是源代码中选定的类协作的代码,是以开发效率换运行效率的一种手段。这使反射成为构建灵活应用的主要工具。 -------------------- ## 反射的作用 ## 反射可以: * 实现跨平台兼容,比如JDK中的SocketImpl的实现 * 通过xml或注解,实现依赖注入(DI),注解处理,动态代理,单元测试等功能。比如Retrofit、Spring或者Dagger -------------------- ## Java Class文件结构介绍 ## 在\*.class文件中,以Byte流的形式进行Class的存储,通过一系列Load,Parse后,Java代码实际上可以映射为下图的结构体,这里可以用javap命令或者IDE插件进行查看。 typedef struct { u4 magic;/*0xCAFEBABE*/ u2 minor_version; /*网上有表可查*/ u2 major_version; /*网上有表可查*/ u2 constant_pool_count; cp_info constant_pool[constant_pool_count-1]; u2 access_flags; u2 this_class; u2 super_class; u2 interfaces_count; u2 interfaces[interfaces_count]; //重要 u2 fields_count; field_info fields[fields_count]; //重要 u2 methods_count; method_info methods[methods_count]; u2 attributes_count; attribute_info attributes[attributes_count]; }ClassBlock; 常量池(constant pool):类似于C中的DATA段与BSS段,提供常量、字符串、方法名等值或者符号(可以看作偏移定值的指针)的存放 access\_flags: 对Class的flag修饰 def enum { ACC_PUBLIC = 0x0001, ACC_FINAL = 0x0010, ACC_SUPER = 0x0020, ACC_INTERFACE = 0x0200, ACC_ACSTRACT = 0x0400 }AccessFlag this class/super class/interface: 一个长度为u2的指针,指向常量池中真正的地址,将在Link阶段进行符号解引。 filed: 字段信息,结构体如下 typedef struct fieldblock { char *name; char *type; char *signature; u2 access_flags; u2 constant; union { union { char data[8]; uintptr_t u; long long l; void *p; int i; } static_value; u4 offset; } u; } FieldBlock; method: 提供descriptor, access\_flags, Code等索引,并指向常量池: 它的结构体如下,详细在这里 method_info { u2 access_flags; u2 name_index; //the parameters that the method takes and the //value that it return u2 descriptor_index; u2 attributes_count; attribute_info attributes[attributes_count]; } -------------------- ## Java Class文件加载过程 ## ### Classloader加载过程 ### ClassLoader用于加载、连接、缓存Class,可以通过纯Java或者native进行实现。在JVM的native代码中,ClassLoader内部维护着一个线程安全的HashTable String,Class>,用于实现对Class字节流解码后的缓存,如果HashTable中已经有了缓存,则直接返回缓存;反之,在获得类名后,通过读取文件、网络上的class字节流反序列化为JVM中native的C结构体,接着malloc内存,并将指针缓存在HashTable中。 下面是非数组情况下ClassLoader的流程 find/load: 将文件反序列化为C结构体。 link: 根据Class结构体常量池进行符号的解引。比如对象计算内存空间,创建方法表,native invoker,接口方法表,finalizer函数等工作。 ### 初始化过程 ### 当ClassLoader加载Class结束后,将进行Class的初始化操作。主要执行clinit()>的静态代码段与静态变量(取决于源码顺序)。 public class Sample { //step.1 static int b = 2; //step.2 static { b = 3; } public static void main(String[] args) { Sample s = new Sample(); System.out.println(s.b); //b=3 } } -------------------- ## 反射在native中的应用 ## 反射在Java中可以直接调用,不过最终调用的仍是native方法,以下为主流反射操作的实现。 ### Class.forName的实现 ### Class.forName可以通过包名寻找Class对象,比如Class.forName(“java.lang.String”)。 在JDK的源码实现中,可以发现最终调用的是native方法forName0(),它在JVM中调用的实际是findClassFromClassLoader(),原理与ClassLoader的流程一样,具体实现已经在上面介绍过了。 ### getDeclaredFields的实现 ### 在JDK源码中,可以知道class.getDeclaredFields()方法实际调用的是native方法getDeclaredFields0(),它在JVM主要实现步骤如下: 根据Class结构体信息,获取field\_count与fields\[\]字段,这个字段早已在load过程中被放入了 根据field\_count的大小分配内存、创建数组 将数组进行forEach循环,通过fields\[\]中的信息依次创建Object对象 返回数组指针 主要慢在如下方面: 创建、计算、分配数组对象 对字段进行循环赋值 ### Method.invoke的实现 ### 以下为无同步、无异常的情况下调用的步骤 创建Frame 如果对象flag为native,交给native\_handler进行处理 在frame中执行java代码 弹出Frame 返回执行结果的指针 主要慢在如下方面: 需要完全执行ByteCode而缺少JIT等优化 检查参数非常多,这些本来可以在编译器或者加载时完成 ### class.newInstance的实现 ### 检测权限、预分配空间大小等参数 创建Object对象,并分配空间 通过Method.invoke调用构造函数(()) 返回Object指针 主要慢在如下方面: 参数检查不能优化或者遗漏 ()的查表 Method.invoke本身耗时
还没有评论,来说两句吧...