Java反射在JVM中的实现

本文探讨了Java反射的概念,详细介绍了Class文件结构、加载过程以及反射在JVM中的实现,包括Class.forName、getDeclaredFields、Method.invoke和class.newInstance的方法实现,揭示了反射在提高开发灵活性的同时,其潜在的性能影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

什么是反射?

反射使程序代码能够接入装载到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本身耗时

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值