本文目录 什么是Java反射,有什么用? Java Class文件的结构 Java Class加载的过程 反射在native的实现 附录 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插件进行查看。 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;
3. Java Class加载的过程 Class的加载主要分为两步
3.1. Classloader加载过程 ClassLoader用于加载、连接、缓存Class,可以通过纯Java或者native进行实现。在JVM的native代码中,ClassLoader内部维护着一个线程安全的HashTable<String,Class>,用于实现对Class字节流解码后的缓存,如果HashTable中已经有了缓存,则直接返回缓存;反之,在获得类名后,通过读取文件、网络上的class字节流反序列化为JVM中native的C结构体,接着malloc内存,并将指针缓存在HashTable中。 下面是非数组情况下ClassLoader的流程
Class反序列化的流程
3.2. 初始化过程 当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 } }
在完成初始化后,就是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主要实现步骤如下
4.3. Method.invoke的实现 以下为无同步、无异常的情况下调用的步骤
4.4. class.newInstance的实现
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: 加载自己写的或者引用的第三方包,这个最常见 例子如下 //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 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上认为反射比较慢的程序员主要有如下看法
当然,现代JVM也不是非常慢了,它能够对反射代码进行缓存以及通过方法计数器同样实现JIT优化,所以反射不一定慢。 更重要的是,很多情况下,你自己的代码才是限制程序的瓶颈。因此,在开发效率远大于运行效率的的基础上,大胆使用反射,放心开发吧。
|
|