Java反射在JVM中的实现

深碍√TFBOYSˉ_ 2022-07-16 09:59 255阅读 0赞

什么是反射?

反射使程序代码能够接入装载到JVM中的类的内部信息,允许在编写与执行时,而不是源代码中选定的类协作的代码,是以开发效率换运行效率的一种手段。这使反射成为构建灵活应用的主要工具。


反射的作用

反射可以:

  • 实现跨平台兼容,比如JDK中的SocketImpl的实现
  • 通过xml或注解,实现依赖注入(DI),注解处理,动态代理,单元测试等功能。比如Retrofit、Spring或者Dagger

Java Class文件结构介绍

在*.class文件中,以Byte流的形式进行Class的存储,通过一系列Load,Parse后,Java代码实际上可以映射为下图的结构体,这里可以用javap命令或者IDE插件进行查看。

  1. typedef struct {
  2. u4 magic;/*0xCAFEBABE*/
  3. u2 minor_version; /*网上有表可查*/
  4. u2 major_version; /*网上有表可查*/
  5. u2 constant_pool_count;
  6. cp_info constant_pool[constant_pool_count-1];
  7. u2 access_flags;
  8. u2 this_class;
  9. u2 super_class;
  10. u2 interfaces_count;
  11. u2 interfaces[interfaces_count];
  12. //重要
  13. u2 fields_count;
  14. field_info fields[fields_count];
  15. //重要
  16. u2 methods_count;
  17. method_info methods[methods_count];
  18. u2 attributes_count;
  19. attribute_info attributes[attributes_count];
  20. }ClassBlock;

常量池(constant pool):类似于C中的DATA段与BSS段,提供常量、字符串、方法名等值或者符号(可以看作偏移定值的指针)的存放
access_flags: 对Class的flag修饰

  1. def enum {
  2. ACC_PUBLIC = 0x0001,
  3. ACC_FINAL = 0x0010,
  4. ACC_SUPER = 0x0020,
  5. ACC_INTERFACE = 0x0200,
  6. ACC_ACSTRACT = 0x0400
  7. }AccessFlag

this class/super class/interface: 一个长度为u2的指针,指向常量池中真正的地址,将在Link阶段进行符号解引。

filed: 字段信息,结构体如下

  1. typedef struct fieldblock {
  2. char *name;
  3. char *type;
  4. char *signature;
  5. u2 access_flags;
  6. u2 constant;
  7. union {
  8. union {
  9. char data[8];
  10. uintptr_t u;
  11. long long l;
  12. void *p;
  13. int i;
  14. } static_value;
  15. u4 offset;
  16. } u;
  17. } FieldBlock;

method: 提供descriptor, access_flags, Code等索引,并指向常量池:
它的结构体如下,详细在这里

  1. method_info {
  2. u2 access_flags;
  3. u2 name_index;
  4. //the parameters that the method takes and the
  5. //value that it return
  6. u2 descriptor_index;
  7. u2 attributes_count;
  8. attribute_info attributes[attributes_count];
  9. }

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()>的静态代码段与静态变量(取决于源码顺序)。

  1. public class Sample {
  2. //step.1
  3. static int b = 2;
  4. //step.2
  5. static {
  6. b = 3;
  7. }
  8. public static void main(String[] args) {
  9. Sample s = new Sample();
  10. System.out.println(s.b);
  11. //b=3
  12. }
  13. }

反射在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本身耗时

发表评论

表情:
评论列表 (有 0 条评论,255人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Java实现方式

    所谓反射,是指在运行时状态中,获取类中的属性和方法,以及调用其中的方法的一种机制。这种机制的作用在于获取运行时才知道的类(Class)及其中的属性(Field)、方法(Meth

    相关 JavaJVM实现

    什么是反射? 反射使程序代码能够接入装载到JVM中的类的内部信息,允许在编写与执行时,而不是源代码中选定的类协作的代码,是以开发效率换运行效率的一种手段。这使反射成为构建

    相关 Java深入浅出

    刚开始接触反射这个概念,感觉反射这个机制很复杂很难懂,所以在这篇文章中对java的反射机制以个人的理解总结归纳。 1. 什么是反射? 什么是反射?在官方文档中是这样说的