反射作为一种高级技术,虽然在业务开发中很少直接编写相关代码,但实际存在于 Java 开发中的方方面面。

  1. Spring Framework
    • 依赖注入:使用反射实例化对象、设置属性和调用方法,实现IoC。
    • AOP:通过反射在方法调用前后动态添加逻辑,如日志记录和事务管理。
  2. Hibernate
    • 对象关系映射(ORM):利用反射读取和写入实体类属性,实现Java对象与数据库表的映射。
  3. JUnit测试框架:使用反射发现和执行测试方法,支持注解定义测试。

  4. Java 标准库中的动态代理

    • java.lang.reflect.Proxy 使用反射创建代理对象,并将方法调用委托给 InvocationHandler
  5. RPC(远程过程调用)
    • 动态代理实现远程接口,使本地调用像调用本地方法一样调用远程服务。
  6. Java 序列化
    • 反射获取对象字段及其值,将对象转换为字节流。
  7. JSON/XML 解析库
    • 如 Jackson、Gson 和 JAXB,使用反射将 JSON 或 XML 数据与 Java 对象相互转换。
  8. 开发工具和 IDE:使用反射检查和操作运行中的应用程序,获取对象信息、调用方法等。

0. 什么是反射

反射指的是允许程序在运行时动态地检查和操作类、方法、字段等。通过反射,程序可以在运行时了解一个类的结构,并且可以创建对象、调用方法、访问和修改字段等。

如何理解“反”

“反”在反射中的含义可以理解为“反向操作”或“反查”。

通常,我们在编写代码时会显式地调用方法、访问字段或创建对象,比如:

1
2
Person person = new Person();
person.setName("Alice");

这是正向操作,即程序明确知道要操作的类和方法。这种方式称为编译时绑定,因为所有的类型、方法和变量调用在编写代码时就已确定,并在编译期间已经建立了它们的关联。这种方式依赖于静态类型检查,能够在编译阶段捕获许多错误,同时也提高了代码的执行效率。

而反射则相当于反向操作,程序在编写代码时并不知道具体的类和方法(编码时只是给出了“字面名称”,而不是实际的类和方法),而是在运行时通过反射机制获取类的信息并进行操作。
这种方式称为运行时绑定,因为代码在运行时才确定要调用的具体类、方法或属性。

1
2
3
4
Class<?> clazz = Class.forName("Person");
Object person = clazz.newInstance();
Method setNameMethod = clazz.getMethod("setName", String.class);
setNameMethod.invoke(person, "Alice");

在这个反射的例子中,我们没有在代码中直接调用 Person 类或其方法。相反,我们在运行时加载了类,检索了构造函数和方法,并动态地调用了它们。

“反”与“正”的对比

  • 性能:正常方法(编译时绑定)通常性能更高,因为它们在编译时就已经解决了大部分引用问题;而反射(运行时绑定)在查找和调用过程中需要消耗额外的资源,性能相对较低。
  • 类型安全:正常方法在编译时就能检查类型安全性,而反射则需要程序员在运行时手动确保类型的正确性,这增加了出错的风险。
  • 灵活性:反射提供了极高的灵活性,可以在运行时动态加载和操作类,这在开发通用框架或需要大量运行时信息的复杂系统中非常有用。

反射的工作原理

反射基于两部分内容实现

  1. java.lang.Class 类
  2. java.lang.reflect 包中的类(如 Method, Field, Constructor 等)来实现

1. Class 类

Class类也是一个实实在在的类,存在于JDK的java.lang包中。

1
2
3
4
5
public final class Class<T> implements java.io.Serializable,
                              GenericDeclaration,
                              Type,
                              AnnotatedElement {
}

Java 编译器将 Java 源代码(.java 文件)编译后 ,产生.class 文件, 保存了类的字节码及相关的元数据。

当 JVM 运行加载一个类时,它会读取相应的 .class 文件,并根据字节码信息生成一个 Class 对象表示该类。

这个 Class 对象是 JVM 内存中的一个运行时数据结构,包含了类的详细信息,如类名、修饰符、字段、方法、接口、父类等。

Class 对象在 JVM 的方法区(Java 8 及之前)或元空间(Java 8 及之后)中分配内存,存储类的元数据。

无论创建多少个该类的实例对象,它们都共享同一个 Class 对象。

1.1 .class 文件的内容

.class 文件包含以下内容:

  • 魔数(Magic Number):用于识别文件格式。
  • 版本信息:表示编译器生成该 .class 文件时使用的 JDK 版本。
  • 常量池(Constant Pool):保存字面量和符号引用。
  • 访问标志(Access Flags):描述类或接口的访问级别和其他属性。
  • 类、父类和接口信息:包括类名、父类名和实现的接口。
  • 字段(Fields):类的字段信息。
  • 方法(Methods):类的方法信息。
  • 属性(Attributes):类的其他属性信息,如注解、源文件信息等。

1.2 Class 对象的生成时机

当 JVM 运行加载一个类,读取相应的 .class 文件 时,会进行以下步骤:

  1. 加载(Loading):通过类加载器读取 .class 文件的字节码。
  2. 链接(Linking)
    • 验证(Verification):确保字节码符合 JVM 规范,不会破坏 JVM 的安全性。
    • 准备(Preparation):为类的静态变量分配内存,并将其初始化为默认值。
    • 解析(Resolution):将常量池中的符号引用替换为直接引用。
  3. 初始化(Initialization):执行类的静态初始化块和静态变量的初始化。

在这些步骤完成之后,JVM 会在内存中创建一个 Class 对象来表示这个类。这个 Class 对象包含了类的所有元数据,并且无论创建多少个该类的实例对象,它们都共享同一个 Class 对象。

[[JVM#类加载-加载,生成Class对象]]

1.3 Class 对象的唯一性

对于同一个类,无论创建多少个实例对象,它们共享同一个 Class 对象。

类加载器在决定 Class 对象的唯一性上起着关键作用。如果同一个类被不同的类加载器加载,那么它们会有不同的 Class 对象。

比如创建一个Shapes类,那么,JVM就会创建一个Shapes对应Class类的Class对象,无论创建多少个实例对象,在JVM中都只有一个Class对象。

可以通过以下代码验证唯一性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
public class ClassUniqueDemo {

public static void main(String[] args) {
// 创建多个实例对象
Person person1 = new Person("Alice", 30);
Person person2 = new Person("Bob", 25);

// 获取 Class 对象
Class<?> class1 = person1.getClass();
Class<?> class2 = person2.getClass();
Class<?> class3 = Person.class;

// 打印 Class 对象的哈希码
System.out.println("class1: " + class1.hashCode());
System.out.println("class2: " + class2.hashCode());
System.out.println("class3: " + class3.hashCode());

// 验证所有实例对象的 Class 对象是否相同
System.out.println("class1 == class2: " + (class1 == class2));
System.out.println("class1 == class3: " + (class1 == class3));
}
}

class Person {
private String name;
private int age;

public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}
}

1.4 关于Class 的一点总结

到这,我们可以总结一下关于Class 的相关信息

  • Class类是Java中的一种类,与关键字class不同。
  • Class对象是由Java虚拟机(JVM)加载的,它代表Java中的类或接口。
  • 每个通过class关键字定义的类,在编译后都会生成一个.class文件。JVM加载这个.class文件,并生成对应的Class对象。
  • 无论一个类创建了多少个实例,所有这些实例都共享同一个Class对象。这是因为Class对象表示类的元数据,存储了类的结构信息,而实例则是类的具体实现。
  • Class类的构造函数是私有的,确保了Class对象只能由JVM内部创建。开发者不能直接通过构造函数创建Class对象,而是通过类加载机制间接地获得Class对象。

2. 使用反射访问类的各个组成部分

2.1 获取Class 对象

获取一个类的 Class 对象有4种, 但是前3种更常用

  1. 通过类名

    1
    2
    Class<?> clazz = ClassName.class;

  2. 通过对象实例:

    1
    2
    Class<?> clazz = objectInstance.getClass();

  3. forName 全限定类名:

    1
    Class<?> clazz = Class.forName("com.example.ClassName");
  4. 通过 ClassLoader 获取 Class 对象(Spring bean 创建过程中会用到这种方式)
1
2
3
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Class<?> clazz = classLoader.loadClass("com.example.MyClass");

Class.forName vs ClassLoader.loadClass

Class.forNameClassLoader.loadClass都是通过全限定名获取Class对象的方法,但它们在类的初始化行为、使用场景和加载器选择上存在显著区别:

  • 类的初始化Class.forName默认会触发类的初始化,而ClassLoader.loadClass默认不会。
  • 使用场景Class.forName适用于简单场景和需要触发初始化的情况;ClassLoader.loadClass适用于需要控制类加载器和避免类初始化的情况。
  • 加载器的选择Class.forName使用当前线程的上下文ClassLoader,而ClassLoader.loadClass可以显式指定ClassLoader

2.2 获取构造函数 Constructor

Class 类中有以下4个方法来获取构造函数

  • getConstructors():获取所有公共/public构造函数。
  • getConstructor(Class<?>... parameterTypes):获取指定参数类型的公共构造函数。
  • getDeclaredConstructors():获取所有声明的构造函数(包括公共、保护、默认(包)访问和私有)。
  • getDeclaredConstructor(Class<?>... parameterTypes):获取指定参数类型的声明的构造函数(包括公共、保护、默认(包)访问和私有)。

2.2.1 constructor.newInstance

使用构造函数获取实例化对象
从以上代码示例中可以看出,一般情况下我们使用反射获取一个对象的步骤:

1
2
Constructor<?> constructor = clazz.getConstructor();
Object instance = constructor.newInstance();

2.3 Method

![

  • getMethods():获取所有公共方法,包括继承的方法。
  • getMethod(String name, Class<?>... parameterTypes):获取指定名称和参数类型的公共方法,包括继承的方法。
  • getDeclaredMethods():获取所有声明的方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。
  • getDeclaredMethod(String name, Class<?>... parameterTypes):获取指定名称和参数类型的声明的方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。

2.4 Field

  • getFields():获取所有公共字段,包括继承的公共字段。
  • getField(String name):获取指定名称的公共字段。
  • getDeclaredFields():获取所有声明的字段,包括公共、保护、默认(包)访问和私有字段,但不包括继承的字段。
  • getDeclaredField(String name):获取指定名称的声明的字段,包括公共、保护、默认(包)访问和私有字段,但不包括继承的字段。

2.5 getInterfaces

1
public Class<?>[] getInterfaces()

返回一个 Class 对象数组,表示类或接口实现的所有接口。

2.6 示例代码

以下是一个完整的示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
public class ReflectionExample {
public static void main(String[] args) {
try {
// 获取Class对象
Class<?> clazz = Class.forName("com.example.codingInAction.reflectionPractice.Person");

// 获取所有构造函数
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println("Constructor: " + constructor);
}
// 获取无参构造函数
Constructor<?> constructor1 = clazz.getDeclaredConstructor();

System.out.println("Constructor1: " + constructor1);

Constructor<?> constructor2 = clazz.getDeclaredConstructor(String.class);
System.out.println("Constructor2: " + constructor2);


Constructor<?> constructor3 = clazz.getDeclaredConstructor(int.class);
System.out.println("Constructor3: " + constructor3);

Constructor<?> constructor4 = clazz.getConstructor(String.class, int.class);
System.out.println("Constructor4: " + constructor4);

Object alex = constructor4.newInstance("Alex", 30);
System.out.println(alex);


// 获取所有方法
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
System.out.println("Method: " + method);
}

// 获取指定名称和参数类型的声明的方法
Method setAgeMethod = clazz.getDeclaredMethod("setAge", int.class);
System.out.println("Method: " + setAgeMethod);

// 如果是私有方法,需要设置成 可访问
// setAgeMethod.setAccessible(true);

setAgeMethod.invoke(alex, 35);

Method getAgeMethod = clazz.getDeclaredMethod("getAge");

int age = (Integer) getAgeMethod.invoke(alex);

System.out.println("alex's age is " + age);


// 获取所有字段
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
System.out.println("Field: " + field);
}

// 获取指定名称的声明的字段
Field ageField = clazz.getDeclaredField("age");
System.out.println("Field: " + ageField);
//如果是private, 需要设置成 “可访问”
ageField.setAccessible(true);

System.out.println("Age: " + ageField.get(alex));

} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException |
InvocationTargetException | InstantiationException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
throw new RuntimeException(e);
}
}
}

3. 反射的缺点

3.1 破坏封装性

在面向对象编程中,封装性是指将对象的属性和实现细节隐藏起来,只暴露必要的接口给外部使用。通过访问控制修饰符(如 privateprotectedpublic),类可以控制哪些成员可以被外部访问,从而保护对象的内部状态,确保对象的一致性和安全性。

从上面的代码可以明显看出一个问题, 我们定义的private 构造函数和字段, 通过getDeclared* 和setAccessible 后可以使得原本私有的成员变得可访问,这无疑破坏了priavte 的语义,也就是封装性。

  1. 访问私有方法
  2. 访问私有变量

反射破坏封装性的影响

  1. 安全性问题:私有字段和方法的访问控制被绕过,可能会导致程序的安全性问题。
  2. 对象一致性:私有字段的修改可能会破坏对象的一致性,导致不可预知的行为。例如,某个字段可能需要通过特定的方法进行修改,以确保其值的合法性,而直接修改字段可能会绕过这些检查。
  3. 维护性降低:使用反射访问私有成员会使代码变得难以维护,因为这些操作依赖于类的内部实现,而不是其公开的接口。类的实现细节变化可能会导致反射代码失效。

如何避免反射破坏封装性

  1. 最小化反射的使用:尽量减少反射的使用,尤其是对于私有成员的访问。
  2. 使用适当的访问修饰符:确保类的设计合理,公开必要的接口,私有化不应被外部访问的成员。
  3. 安全管理器:在敏感应用中,使用安全管理器来限制反射操作,防止未经授权的访问。

3.2 效率低

大家都说 Java 反射效率低,你知道原因在哪里么

反射效率低的原因主要在于额外的类型检查和安全检查、方法调用的间接性、缺乏编译时优化以及增加的内存开销。在实际开发中,除非确实需要动态性和灵活性,否则应尽量避免使用反射,以提高代码的性能和可维护性。如果必须使用反射,应尽量减少反射调用的次数,或者在初始化阶段使用反射将结果缓存起来,以降低运行时的开销。

1. 额外的类型检查和安全检查

  1. 类型检查
    • 反射在运行时执行,JVM 需要在每次访问字段或调用方法时进行类型检查,以确保操作的合法性。这与普通方法调用的编译时类型检查相比,增加了额外的开销。
  2. 安全检查
    • 反射需要进行安全检查(如访问控制检查),特别是在设置 setAccessible(true) 时。这些检查在每次反射调用时都会进行,增加了运行时的开销。

2. 方法调用的间接性
普通方法调用是通过直接的字节码指令进行的,JVM 可以进行各种优化,如内联(inlining)、即时编译(JIT)等。而反射调用是通过反射 API 间接调用方法,不能享受这些优化。

3. 缺乏编译时优化
编译时优化可以显著提高代码执行效率,但反射操作是在运行时决定的,因此编译器无法进行这些优化。编译器对普通方法调用可以进行多种优化,如常量折叠、循环展开等,但对反射操作则无能为力。

4. 内存开销
反射操作会带来额外的内存开销,包括:

  • 创建和维护反射对象(如 MethodField 等)。
  • 反射调用时需要创建数组来传递参数和返回值。
  • 反射调用的结果需要进行类型转换,这也会增加内存开销。

4. 源码解析-invoke

重点介绍method.invoke 的实现原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@CallerSensitive
@ForceInline // to ensure Reflection.getCallerClass optimization
@HotSpotIntrinsicCandidate
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
{
// 检查是否需要进行访问控制检查
if (!override) {
// 获取调用者的类
Class<?> caller = Reflection.getCallerClass();
// 检查访问权限,检查调用者是否有权限调用目标方法。如果目标方法是静态的,则第三个参数为 null,否则为目标对象的类。
checkAccess(caller, clazz,
Modifier.isStatic(modifiers) ? null : obj.getClass(),
modifiers);
}

// 获取MethodAccessor实例
// 这是一个volatile字段,确保多线程访问时的可见性。
MethodAccessor ma = methodAccessor; // read volatile
// 如果 methodAccessor 为空,则调用此方法获取一个新的 MethodAccessor 实例。
// MethodAccessor 是实际执行反射调用的接口,其实现由JVM提供,可能是生成的字节码或本地代码。
if (ma == null) {
ma = acquireMethodAccessor();
}

// 使用MethodAccessor调用目标方法
return ma.invoke(obj, args);
}

4.1 优化与性能

通过注解 @ForceInline@HotSpotIntrinsicCandidate,JVM可以对 invoke 方法进行内联优化和本地代码优化,从而减少反射调用的性能开销。

4.2 acquireMethodAccessor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public final class Method extends Executable {

private MethodAccessor acquireMethodAccessor() {
// First check to see if one has been created yet, and take it
// if so
MethodAccessor tmp = null;
if (root != null) tmp = root.getMethodAccessor();
if (tmp != null) {
methodAccessor = tmp;
} else {
// Otherwise fabricate one and propagate it up to the root
tmp = reflectionFactory.newMethodAccessor(this);
setMethodAccessor(tmp);
}

return tmp;
}

}

从以上代码可以看出, 生成MethodAccessor时用到了工厂模式, 说明有多个MethodAccessor,下面来具体看一下MethodAccessor及其实现

4.3 MethodAccessor

MethodAccessor 是一个内部接口,用于抽象反射机制中实际的方法调用实现。具体的实现类有几种,其中包括 DelegatingMethodAccessorImplMethodAccessorImplNativeMethodAccessorImpl。每个实现类都有其特定的作用和实现方式。

其实现可能是动态生成的字节码(如MethodAccessorImpl),或者是通过JNI调用本地代码(如 NativeMethodAccessorImpl)。具体实现可能会在不同的JVM中有所不同,但主要目的是为了提高反射调用的性能。

1
2
3
4
5
public interface MethodAccessor {  
/** Matches specification in {@link java.lang.reflect.Method} */
public Object invoke(Object obj, Object[] args)
throws IllegalArgumentException, InvocationTargetException;
}
  1. NativeMethodAccessorImpl 使用JNI来调用本地方法,具有较高的性能。
  2. MethodAccessorImpl 使用生成字节码的方式来提高性能。
  3. DelegatingMethodAccessorImpl 用于在运行时动态替换 MethodAccessor 实现,以优化反射调用的性能。

4.3.1 NativeMethodAccessorImpl

NativeMethodAccessorImpl 使用 JNI(Java Native Interface)来调用本地方法。它是通过本地代码实现的,在冷启动方面具有较高的效率,但是实际调用是性能较慢。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42


/** Used only for the first few invocations of a Method; afterward,
switches to bytecode-based implementation */

class NativeMethodAccessorImpl extends MethodAccessorImpl {
private final Method method;
private DelegatingMethodAccessorImpl parent;
private int numInvocations;

NativeMethodAccessorImpl(Method method) {
this.method = method;
}

public Object invoke(Object obj, Object[] args)
throws IllegalArgumentException, InvocationTargetException
{
// We can't inflate methods belonging to vm-anonymous classes because
// that kind of class can't be referred to by name, hence can't be
// found from the generated bytecode.
if (++numInvocations > ReflectionFactory.inflationThreshold()
&& !ReflectUtil.isVMAnonymousClass(method.getDeclaringClass())) {
MethodAccessorImpl acc = (MethodAccessorImpl)
new MethodAccessorGenerator().
generateMethod(method.getDeclaringClass(),
method.getName(),
method.getParameterTypes(),
method.getReturnType(),
method.getExceptionTypes(),
method.getModifiers());
parent.setDelegate(acc);
}

return invoke0(method, obj, args);
}

void setParent(DelegatingMethodAccessorImpl parent) {
this.parent = parent;
}

private static native Object invoke0(Method m, Object obj, Object[] args);
}

4.3.2 MethodAccessorImpl

MethodAccessorImpl 是通过生成字节码来提高反射调用的性能。具体实现涉及生成动态字节码,以直接调用目标方法。

1
2
3
4
5
6
abstract class MethodAccessorImpl extends MagicAccessorImpl  
implements MethodAccessor {
/** Matches specification in {@link java.lang.reflect.Method} */
public abstract Object invoke(Object obj, Object[] args)
throws IllegalArgumentException, InvocationTargetException;
}

4.3.3 DelegatingMethodAccessorImpl

DelegatingMethodAccessorImpl 是一种委托实现,它将调用委托给另一个 MethodAccessor 实例。这种设计通常用于在运行时动态替换 MethodAccessor 实现,以提高性能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class DelegatingMethodAccessorImpl extends MethodAccessorImpl {
private MethodAccessorImpl delegate;

DelegatingMethodAccessorImpl(MethodAccessorImpl delegate) {
setDelegate(delegate);
}

public Object invoke(Object obj, Object[] args)
throws IllegalArgumentException, InvocationTargetException
{
return delegate.invoke(obj, args);
}

void setDelegate(MethodAccessorImpl delegate) {
this.delegate = delegate;
}
}

4.4 invoke 实现的两种方式

MethodAccessor的3个实现类可以看出,invoke 方法内部的实现涉及到两种不同的方式:原生(native)实现方式和Java字节码实现方式。这两种实现方式旨在优化不同阶段的反射调用性能。下面详细解释这两种实现方式以及它们在invoke方法中的动态切换。

4.4.1 初始实现:Native 实现方式

在反射调用的初始阶段,使用JNI(Java Native Interface)/ NativeMethodAccessorImpl 方式可以==快速生成==一个可调用的反射方法。这是因为JNI调用直接利用了现有的本地代码实现,不需要额外的字节码生成过程。

这种方式启动快,适合初次和少量调用。

生成速度

  • 生成快:初次调用反射方法时,使用JNI(Java Native Interface)方式可以快速生成一个可调用的反射方法。这是因为JNI调用直接利用了现有的本地代码实现,不需要额外的字节码生成过程。

运行性能

  • 运行性能相对较慢:虽然JNI方式生成速度快,但在实际调用过程中,反射调用通过JNI进行本地方法调用,没有针对Java方法调用的优化。在需要多次调用同一个反射方法时,运行性能会比较低。

4.4.2 优化阶段:Java 字节码实现方式

当反射调用达到一定频率时,JVM会动态切换到基于Java 字节码 MethodAccessorImpl实现方式。这种方式通过动态生成字节码来直接调用目标方法。

1. 生成速度

  • 生成较慢:使用字节码生成方式需要在运行时动态生成和编译Java字节码,这个过程相对复杂,初次生成时会比JNI方式慢。

    2. 调用过程

  • 运行时有性能优化:一旦生成了字节码实现,JVM的即时编译器(JIT)可以对这些字节码进行优化,从而提高执行效率。

    3. 运行性能

  • 执行比较快:生成字节码后,JVM的即时编译器(JIT)可以对这些字节码进行优化,从而提高执行效率。特别是在多次调用时优势明显。

根据以上2种方式的特点介绍,可以总结为

  • JNI方式生成快,但是运行性能相对较慢。
  • 字节码生成方式生成较慢,但生成后调用时性能更高,因为JVM的即时编译器可以对字节码进行优化。
  • 动态切换所以当反射调用达到一定次数后,更希望执行速度得到优化,JVM就会动态切换,会从JNI方式切换到字节码生成方式,以提高运行时性能。