本文目录
反射使程序代码能够接入装载到JVM中的类的内部信息,允许在编写与执行时,而不是源代码中选定的类协作的代码,是以开发效率换运行效率的一种手段。这使反射成为构建灵活应用的主要工具。
反射可以:
在*.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;
|
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
|
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;
|
它的结构体如下,详细在这里
123456789method_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];
}
以上具体内容可以参考
- JVM文档
- 周志明的《深入理解Java虚拟机》,少见的国内精品书籍
- 一些国外教程的解析
Class的加载主要分为两步
()
初始化。 ClassLoader用于加载、连接、缓存Class,可以通过纯Java或者native进行实现。在JVM的native代码中,ClassLoader内部维护着一个线程安全的HashTable
,用于实现对Class字节流解码后的缓存,如果HashTable中已经有了缓存,则直接返回缓存;反之,在获得类名后,通过读取文件、网络上的class字节流反序列化为JVM中native的C结构体,接着malloc内存,并将指针缓存在HashTable中。
下面是非数组情况下ClassLoader的流程
当ClassLoader加载Class结束后,将进行Class的初始化操作。主要执行
的静态代码段与静态变量(取决于源码顺序)。
1234567891011121314public
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
}
}
具体参考如下:
- When and how a Java class is loaded and initialized?
- The Lifetime of a Type
在完成初始化后,就是Object的构造
了,本文暂不讨论。
反射在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等优化
- 检查参数非常多,这些本来可以在编译器或者加载时完成
()
)主要慢在如下方面
- 参数检查不能优化或者遗漏
的查表
() - 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: 加载自己写的或者引用的第三方包,这个最常见
例子如下
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,实现加密、压缩、热部署等功能,这个是大坑,晚点再开。
在Stackoverflow上认为反射比较慢的程序员主要有如下看法
当然,现代JVM也不是非常慢了,它能够对反射代码进行缓存以及通过方法计数器同样实现JIT优化,所以反射不一定慢。
更重要的是,很多情况下,你自己的代码才是限制程序的瓶颈。因此,在开发效率远大于运行效率的的基础上,大胆使用反射,放心开发吧。