反射使程序代码可以接入装载到JVM中的类的内部信息,容许在编写与执行时,而不是源代码中选定的类协做的代码,是以开发效率换运行效率的一种手段。这使反射成为构建灵活应用的主要工具。html
反射能够:java
在*.class文件中,以Byte流的形式进行Class的存储,经过一系列Load,Parse后,Java代码实际上能够映射为下图的结构体,这里能够用javap
命令或者IDE插件进行查看。android
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;
typedef enum { ACC_PUBLIC = 0x0001, ACC_FINAL = 0x0010, ACC_SUPER = 0x0020, ACC_INTERFACE = 0x0200, ACC_ACSTRACT = 0x0400 }AccessFlag
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]; }
以上具体内容能够参考缓存
Class的加载主要分为两步安全
<clinit>()
初始化。ClassLoader用于加载、链接、缓存Class,能够经过纯Java或者native进行实现。在JVM的native代码中,ClassLoader内部维护着一个线程安全的HashTable<String,Class>
,用于实现对Class字节流解码后的缓存,若是HashTable中已经有了缓存,则直接返回缓存;反之,在得到类名后,经过读取文件、网络上的class字节流反序列化为JVM中native的C结构体,接着malloc内存,并将指针缓存在HashTable中。网络
下面是非数组状况下ClassLoader的流程oracle
Class反序列化的流程app
当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>
了,本文暂不讨论。
反射在Java中能够直接调用,不过最终调用的还是native方法,如下为主流反射操做的实现。
Class.forName能够经过包名寻找Class对象,好比Class.forName("java.lang.String")
。
在JDK的源码实现中,能够发现最终调用的是native方法forName0()
,它在JVM中调用的实际是findClassFromClassLoader()
,原理与ClassLoader的流程同样,具体实现已经在上面介绍过了。
在JDK源码中,能够知道class.getDeclaredFields()
方法实际调用的是native方法getDeclaredFields0()
,它在JVM主要实现步骤以下
field_count
与fields[]
字段,这个字段早已在load过程当中被放入了field_count
的大小分配内存、建立数组fields[]
中的信息依次建立Object对象主要慢在以下方面
- 建立、计算、分配数组对象
- 对字段进行循环赋值
如下为无同步、无异常的状况下调用的步骤
主要慢在以下方面
- 须要彻底执行ByteCode而缺乏JIT等优化
- 检查参数很是多,这些原本能够在编译器或者加载时完成
<init>()
)主要慢在以下方面
- 参数检查不能优化或者遗漏
<init>()
的查表- Method.invoke自己耗时
初次学习JVM时,不建议去看Android Art、Hotspot等重量级JVM的实现,它内部的防护代码不少,还有android与libcore、bionic库紧密耦合,以及分层、内联甚至能把编译器的语义分析绕进去,所以找一个教学用的、嵌入式小型的JVM有利于节约本身的时间。由于之前折腾过OpenWrt,听过有大神推荐过jamvm,只有不到200个源文件,很是适合学习。
在工具的选择上,我的推荐SourceInsight。对比了好几个工具clion,vscode,sublime,sourceinsight,只有sourceinsight对索引、符号表的解析最准确。
参考这里
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,实现加密、压缩、热部署等功能,这个是大坑,晚点再开。
在Stackoverflow上认为反射比较慢的程序员主要有以下见解
固然,现代JVM也不是很是慢了,它可以对反射代码进行缓存以及经过方法计数器一样实现JIT优化,因此反射不必定慢。
更重要的是,不少状况下,你本身的代码才是限制程序的瓶颈。所以,在开发效率远大于运行效率的的基础上,大胆使用反射,放心开发吧。