Java反射在JVM的实现

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 本文目录 什么是Java反射,有什么用?Java Class文件的结构Java Class加载的过程反射在native的实现附录 1. 什么是Java反射,有什么用? 反射使程序代码能够接入装载到JVM中的类的内部信息,允许在编写与执行时,而不是源代码中选定的类协作的代码,是以开发效率换运行效率的一种手段。

本文目录

  1. 什么是Java反射,有什么用?
  2. Java Class文件的结构
  3. Java Class加载的过程
  4. 反射在native的实现
  5. 附录

1. 什么是Java反射,有什么用?

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

反射可以:

  1. 调用一些私有方法,实现黑科技。比如双卡短信发送、设置状态栏颜色、自动挂电话等。
  2. 实现序列化与反序列化,比如PO的ORM,Json解析等。
  3. 实现跨平台兼容,比如JDK中的SocketImpl的实现
  4. 通过xml或注解,实现依赖注入(DI),注解处理,动态代理,单元测试等功能。比如Retrofit、Spring或者Dagger

2. Java Class文件的结构

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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修饰
    1
    2
    3
    4
    5
    6
    7
    typedef 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: 字段信息,结构体如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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等索引,并指向常量池:

它的结构体如下,详细在这里

1
2
3
4
5
6
7
8
9
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];
  }

以上具体内容可以参考

  1. JVM文档
  2. 周志明的《深入理解Java虚拟机》,少见的国内精品书籍
  3. 一些国外教程的解析

3. Java Class加载的过程

Class的加载主要分为两步

  • 第一步通过ClassLoader进行读取、连结操作
  • 第二步进行Class的<clinit>()初始化。

3.1. Classloader加载过程

ClassLoader用于加载、连接、缓存Class,可以通过纯Java或者native进行实现。在JVM的native代码中,ClassLoader内部维护着一个线程安全的HashTable<String,Class>,用于实现对Class字节流解码后的缓存,如果HashTable中已经有了缓存,则直接返回缓存;反之,在获得类名后,通过读取文件、网络上的class字节流反序列化为JVM中native的C结构体,接着malloc内存,并将指针缓存在HashTable中。

下面是非数组情况下ClassLoader的流程

  • find/load: 将文件反序列化为C结构体。

Class反序列化的流程
  • link: 根据Class结构体常量池进行符号的解引。比如对象计算内存空间,创建方法表,native invoker,接口方法表,finalizer函数等工作。

3.2. 初始化过程

当ClassLoader加载Class结束后,将进行Class的初始化操作。主要执行<clinit()>的静态代码段与静态变量(取决于源码顺序)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
   }
}

具体参考如下:

在完成初始化后,就是Object的构造<init>了,本文暂不讨论。

4. 反射在native的实现

反射在Java中可以直接调用,不过最终调用的仍是native方法,以下为主流反射操作的实现。

4.1. Class.forName的实现

Class.forName可以通过包名寻找Class对象,比如Class.forName("java.lang.String")
在JDK的源码实现中,可以发现最终调用的是native方法forName0(),它在JVM中调用的实际是findClassFromClassLoader(),原理与ClassLoader的流程一样,具体实现已经在上面介绍过了。

4.2. getDeclaredFields的实现

在JDK源码中,可以知道class.getDeclaredFields()方法实际调用的是native方法getDeclaredFields0(),它在JVM主要实现步骤如下

  1. 根据Class结构体信息,获取field_countfields[]字段,这个字段早已在load过程中被放入了
  2. 根据field_count的大小分配内存、创建数组
  3. 将数组进行forEach循环,通过fields[]中的信息依次创建Object对象
  4. 返回数组指针

主要慢在如下方面

  1. 创建、计算、分配数组对象
  2. 对字段进行循环赋值

4.3. Method.invoke的实现

以下为无同步、无异常的情况下调用的步骤

  1. 创建Frame
  2. 如果对象flag为native,交给native_handler进行处理
  3. 在frame中执行java代码
  4. 弹出Frame
  5. 返回执行结果的指针

主要慢在如下方面

  1. 需要完全执行ByteCode而缺少JIT等优化
  2. 检查参数非常多,这些本来可以在编译器或者加载时完成

4.4. class.newInstance的实现

  1. 检测权限、预分配空间大小等参数
  2. 创建Object对象,并分配空间
  3. 通过Method.invoke调用构造函数(<init>())
  4. 返回Object指针

主要慢在如下方面

  1. 参数检查不能优化或者遗漏
  2. <init>()的查表
  3. Method.invoke本身耗时

5. 附录

5.1. JVM与源码阅读工具的选择

初次学习JVM时,不建议去看Android Art、Hotspot等重量级JVM的实现,它内部的防御代码很多,还有android与libcore、bionic库紧密耦合,以及分层、内联甚至能把编译器的语义分析绕进去,因此找一个教学用的、嵌入式小型的JVM有利于节约自己的时间。因为以前折腾过OpenWrt,听过有大神推荐过jamvm,只有不到200个源文件,非常适合学习。

在工具的选择上,个人推荐SourceInsight。对比了好几个工具clion,vscode,sublime,sourceinsight,只有sourceinsight对索引、符号表的解析最准确。

5.2. 关于几个ClassLoader

参考这里

ClassLoader0:native的classloader,在JVM中用C写的,用于加载rt.jar的包,在Java中为空引用。

ExtClassLoader: 用于加载JDK中额外的包,一般不怎么用

AppClassLoader: 加载自己写的或者引用的第三方包,这个最常见

例子如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//sun.misc.Launcher$AppClassLoader@4b67cf4d
//which class you create or jars from thirdParty
//第一个非常有歧义,但是它的确是AppClassLoader
ClassLoader.getSystemClassLoader();
com.test.App.getClass().getClassLoader();
Class.forName( "ccom.test.App" ).getClassLoader()
 
//sun.misc.Launcher$ExtClassLoader@66d3c617
//Class loaded in ext jar
Class.forName( "sun.net.spi.nameservice.dns.DNSNameService" )
 
//null, class loaded in rt.jar
String. class .getClassLoader()
Class.forName( "java.lang.String" ).getClassLoader()
Class.forName( "java.lang.Class" ).getClassLoader()
Class.forName( "apple.launcher.JavaAppLauncher" ).getClassLoader()

最后就是getContextClassLoader(),它在Tomcat中使用,通过设置一个临时变量,可以向子类ClassLoader去加载,而不是委托给ParentClassLoader

1
2
3
4
5
6
7
ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();
try {
     Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
     // call some API that uses reflection without taking ClassLoader param
} finally {
     Thread.currentThread().setContextClassLoader(originalClassLoader);
}

最后还有一些自定义的ClassLoader,实现加密、压缩、热部署等功能,这个是大坑,晚点再开。

5.3. 反射是否慢?

在Stackoverflow上认为反射比较慢的程序员主要有如下看法

  1. 验证等防御代码过于繁琐,这一步本来在link阶段,现在却在计算时进行验证
  2. 产生很多临时对象,造成GC与计算时间消耗
  3. 由于缺少上下文,丢失了很多运行时的优化,比如JIT(它可以看作JVM的重要评测标准之一)

当然,现代JVM也不是非常慢了,它能够对反射代码进行缓存以及通过方法计数器同样实现JIT优化,所以反射不一定慢。

更重要的是,很多情况下,你自己的代码才是限制程序的瓶颈。因此,在开发效率远大于运行效率的的基础上,大胆使用反射,放心开发吧。

目录
相关文章
|
27天前
|
监控 算法 Java
Java虚拟机(JVM)的垃圾回收机制深度解析####
本文深入探讨了Java虚拟机(JVM)的垃圾回收机制,旨在揭示其背后的工作原理与优化策略。我们将从垃圾回收的基本概念入手,逐步剖析标记-清除、复制算法、标记-整理等主流垃圾回收算法的原理与实现细节。通过对比不同算法的优缺点及适用场景,为开发者提供优化Java应用性能与内存管理的实践指南。 ####
|
19天前
|
监控 算法 Java
Java虚拟机(JVM)垃圾回收机制深度剖析与优化策略####
本文作为一篇技术性文章,深入探讨了Java虚拟机(JVM)中垃圾回收的工作原理,详细分析了标记-清除、复制算法、标记-压缩及分代收集等主流垃圾回收算法的特点和适用场景。通过实际案例,展示了不同GC(Garbage Collector)算法在应用中的表现差异,并针对大型应用提出了一系列优化策略,包括选择合适的GC算法、调整堆内存大小、并行与并发GC调优等,旨在帮助开发者更好地理解和优化Java应用的性能。 ####
25 0
|
16天前
|
存储 监控 算法
深入探索Java虚拟机(JVM)的内存管理机制
本文旨在为读者提供对Java虚拟机(JVM)内存管理机制的深入理解。通过详细解析JVM的内存结构、垃圾回收算法以及性能优化策略,本文不仅揭示了Java程序高效运行背后的原理,还为开发者提供了优化应用程序性能的实用技巧。不同于常规摘要仅概述文章大意,本文摘要将简要介绍JVM内存管理的关键点,为读者提供一个清晰的学习路线图。
|
18天前
|
存储 监控 算法
Java虚拟机(JVM)垃圾回收机制深度解析与优化策略####
本文旨在深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法及参数调优方法。通过剖析垃圾回收的生命周期、内存区域划分以及GC日志分析,为开发者提供一套实用的JVM垃圾回收优化指南,助力提升Java应用的性能与稳定性。 ####
|
22天前
|
机器学习/深度学习 监控 算法
Java虚拟机(JVM)的垃圾回收机制深度剖析####
本文深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法、性能调优策略及未来趋势。通过实例解析,为开发者提供优化Java应用性能的思路与方法。 ####
31 1
|
22天前
|
Oracle 安全 Java
深入理解Java生态:JDK与JVM的区分与协作
Java作为一种广泛使用的编程语言,其生态中有两个核心组件:JDK(Java Development Kit)和JVM(Java Virtual Machine)。本文将深入探讨这两个组件的区别、联系以及它们在Java开发和运行中的作用。
48 1
|
26天前
|
监控 Java
Java基础——反射
本文介绍了Java反射机制的基本概念和使用方法,包括`Class`类的使用、动态加载类、获取方法和成员变量信息、方法反射操作、以及通过反射了解集合泛型的本质。同时,文章还探讨了动态代理的概念及其应用,通过实例展示了如何利用动态代理实现面向切面编程(AOP),例如为方法执行添加性能监控。
|
1月前
|
监控 Java 开发者
Java虚拟机(JVM)深度优化指南####
本文深入探讨了Java虚拟机(JVM)的工作原理及其性能优化策略,旨在帮助开发者通过理解JVM的内部机制来提升Java应用的运行效率。不同于传统的技术教程,本文采用案例分析与实战技巧相结合的方式,为读者揭示JVM调优的艺术。 ####
58 8
|
1月前
|
Java
Java的反射
Java的反射。
27 2
|
28天前
|
监控 算法 Java
深入理解Java虚拟机(JVM)的垃圾回收机制
【10月更文挑战第21天】 本文将带你深入了解Java虚拟机(JVM)的垃圾回收机制,包括它的工作原理、常见的垃圾收集算法以及如何优化JVM垃圾回收性能。通过本文,你将对JVM垃圾回收有一个全新的认识,并学会如何在实际开发中进行有效的调优。
41 0
下一篇
DataWorks