反射的基础
在Java中,Class类与java.lang.reflect类库一起对反射技术进行了全力的支持。在反射包中,我们常用的类主要有
- Constructor类表示的是Class 对象所表示的类的构造方法,利用它可以在运行时动态创建对象、
- Field表示Class对象所表示的类的成员变量,通过它可以在运行时动态修改成员变量的属性值(包含private)
- Method表示Class对象所表示的类的成员方法,通过它可以动态调用对象的方法(包含private)
下面将对这几个重要类进行分别说明。
Class类对象的获取
在类加载的时候,jvm会创建一个class对象
class对象是可以说是反射中最常用的,获取class对象的方式的主要有三种
- 根据类名:类名.class
- 根据对象:对象.getClass()
- 根据全限定类名:Class.forName(全限定类名)
package com.core.reflect;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class User {
private String name = "init";
private int age;
public User() {
}
public User(String name, int age) {
super();
this.name = name;
this.age = age;
}
private String getName() {
return name;
}
private void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User [name=" + name + ", age=" + age + "]";
}
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
// 获取Class对象的三种方式
log.info("根据类名: \t" + User.class);
log.info("根据对象: \t" + new User().getClass());
log.info("根据全限定类名:\t" + Class.forName("com.core.reflect.User"));
// 常用的方法
Class<User> userClass = User.class;
log.info("获取全限定类名:\t" + userClass.getName());
log.info("获取类名:\t" + userClass.getSimpleName());
log.info("实例化:\t" + userClass.newInstance());
}
}
12:02:24.106 [main] INFO com.core.reflect.User - 根据类名: class com.core.reflect.User
12:02:24.107 [main] INFO com.core.reflect.User - 根据对象: class com.core.reflect.User
12:02:24.107 [main] INFO com.core.reflect.User - 根据全限定类名: class com.core.reflect.User
12:02:24.108 [main] INFO com.core.reflect.User - 获取全限定类名: com.core.reflect.User
12:02:24.108 [main] INFO com.core.reflect.User - 获取类名: User
12:02:24.108 [main] INFO com.core.reflect.User - 实例化: User [name=init, age=0]
Class类的方法
| 方法名 | 说明 |
|---|---|
| forName() | 1.获取Class对象的一个引用,但引用的类还没有加载(该类的第一个对象没有生成)就加载了这个类。 2.为了产生Class引用,forName()立即就进行了初始化。 |
| Object-getClass() | 获取Class对象的一个引用,返回表示该对象的实际类型的Class引用。 |
| getName() | 取全限定的类名(包括包名),即类的完整名字。 |
| getSimpleName() | 获取类名(不包括包名) |
| getCanonicalName() | 获取全限定的类名(包括包名) |
| isInterface() | 判断Class对象是否是表示一个接口 |
| getInterfaces() | 返回Class对象数组,表示Class对象所引用的类所实现的所有接口。 |
| getSupercalss() | 返回Class对象,表示Class对象所引用的类所继承的直接基类。应用该方法可在运行时发现一个对象完整的继承结构。 |
| newInstance() | 返回一个Oject对象,是实现“虚拟构造器”的一种途径。使用该方法创建的类,必须带有无参的构造器。 |
| getFields() | 获得某个类的所有的公共(public)的字段,包括继承自父类的所有公共字段。 类似的还有getMethods和getConstructors。 |
| getDeclaredFields | 获得某个类的自己声明的字段,即包括public、private和proteced,默认但是不包括父类声明的任何字段。类似的还有getDeclaredMethods和getDeclaredConstructors。 |
测试
package com.core.reflect;
import java.lang.reflect.Field;
interface I1 {
}
interface I2 {
}
class Cell {
public int mCellPublic;
}
class Animal extends Cell {
private int mAnimalPrivate;
protected int mAnimalProtected;
int mAnimalDefault;
public int mAnimalPublic;
private static int sAnimalPrivate;
protected static int sAnimalProtected;
static int sAnimalDefault;
public static int sAnimalPublic;
}
class Dog extends Animal implements I1, I2 {
private int mDogPrivate;
public int mDogPublic;
protected int mDogProtected;
private int mDogDefault;
private static int sDogPrivate;
protected static int sDogProtected;
static int sDogDefault;
public static int sDogPublic;
}
public class FieldTest {
public static void main(String[] args) throws IllegalAccessException, InstantiationException {
Class<Dog> dog = Dog.class;
//类名打印
System.out.println(dog.getName()); //com.core.reflect.Dog
System.out.println(dog.getSimpleName()); //Dog
System.out.println(dog.getCanonicalName());//com.core.reflect.Dog
//接口
System.out.println(dog.isInterface()); //false
for (Class iI : dog.getInterfaces()) {
System.out.println(iI);
}
//interface com.core.reflect.I1
//interface com.core.reflect.I2
//父类
System.out.println(dog.getSuperclass());//class com.core.reflect.Animal
//创建对象
Dog d = dog.newInstance();
//字段
for (Field f : dog.getFields()) {
System.out.println(f.getName());
}
//mDogPublic
//sDogPublic
//mAnimalPublic
//sAnimalPublic
//mCellPublic
System.out.println("---------");
for (Field f : dog.getDeclaredFields()) {
System.out.println(f.getName());
}
//mDogPrivate
//mDogPublic
//mDogProtected
//mDogDefault
//sDogPrivate
//sDogProtected
//sDogDefault
//sDogPublic
}
}
getName、getCanonicalName与getSimpleName的区别:
- getSimpleName:只获取类名
- getName:类的全限定名,jvm中Class的表示,可以用于动态加载Class对象,例如Class.forName。
- getCanonicalName:返回更容易理解的表示,主要用于输出(toString)或log打印,大多数情况下和getName一样,但是在内部类、数组等类型的表示形式就不同了。
package com.core.reflect;
public class Test {
private class inner {
}
public static void main(String[] args) throws ClassNotFoundException {
//普通类
System.out.println(Test.class.getSimpleName()); //Test
System.out.println(Test.class.getName()); //com.core.reflect.Test
System.out.println(Test.class.getCanonicalName()); //com.core.reflect.Test
//内部类
System.out.println(inner.class.getSimpleName()); //inner
System.out.println(inner.class.getName()); //com.core.reflect.Test$inner
System.out.println(inner.class.getCanonicalName()); //com.core.reflect.Test$inner
//数组
System.out.println(args.getClass().getSimpleName()); //String[]
System.out.println(args.getClass().getName()); //[Ljava.lang.String;
System.out.println(args.getClass().getCanonicalName()); //java.lang.String[]
//我们不能用getCanonicalName去加载类对象,必须用getName
//Class.forName(inner.class.getCanonicalName()); 报错
Class.forName(inner.class.getName());
}
}
Constructor类及其用法
Constructor类存在于反射包(java.lang.reflect)中,反映的是Class 对象所表示的类的构造方法。
获取Constructor对象是通过Class类中的方法获取的,Class类与Constructor相关的主要方法如下:
| 方法名称 | 方法返回值 | 方法说明 |
|---|---|---|
| forName(String className) | static Class<?> | 返回与带有给定字符串名的类或接口相关联的 Class 对象。 |
| getConstructor(Class<?>... parameterTypes) | Constructor | 返回指定参数类型、具有public访问权限的构造函数对象 |
| getConstructors() | Constructor<?>[] | 返回所有具有public访问权限的构造函数的Constructor对象数组 |
| getDeclaredConstructor(Class<?>... parameterTypes) | Constructor | 返回指定参数类型、所有声明的(包括private)构造函数对象 |
| getDeclaredConstructors() | Constructor<?>[] | 返回所有声明的(包括private)构造函数对象 |
| newInstance() | T | 调用无参构造器创建此 Class 对象所表示的类的一个新实例。 |
下面看一个简单例子来了解Constructor对象的使用:
package com.core.reflect.construction;
public class User {
private int age;
private String name;
public User() {
super();
}
public User(String name) {
super();
this.name = name;
}
/**
* 私有构造
*
* @param age
* @param name
*/
private User(int age, String name) {
super();
this.age = age;
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"age=" + age +
", name='" + name + ''' +
'}';
}
}
package com.core.reflect.construction;
import java.io.Serializable;
import java.lang.reflect.Constructor;
public class ConstructionTest implements Serializable {
public static void main(String[] args) throws Exception {
Class<?> clazz = null;
//获取Class对象的引用
clazz = Class.forName("com.core.reflect.construction.User");
//第一种方法,实例化默认构造方法,User必须无参构造函数,否则将抛异常
User user = (User) clazz.newInstance();
user.setAge(20);
user.setName("Jack");
System.out.println(user);
System.out.println("--------------------------------------------");
//获取带String参数的public构造函数
Constructor cs1 =clazz.getConstructor(String.class);
//创建User
User user1= (User) cs1.newInstance("eva");
user1.setAge(22);
System.out.println("user1:"+user1.toString());
System.out.println("--------------------------------------------");
//取得指定带int和String参数构造函数,该方法是私有构造private
Constructor cs2=clazz.getDeclaredConstructor(int.class,String.class);
//由于是private必须设置可访问
cs2.setAccessible(true);
//创建user对象
User user2= (User) cs2.newInstance(25,"evan");
System.out.println("user2:"+user2.toString());
System.out.println("--------------------------------------------");
//获取所有构造包含private
Constructor<?> cons[] = clazz.getDeclaredConstructors();
// 查看每个构造方法需要的参数
for (int i = 0; i < cons.length; i++) {
//获取构造函数参数类型
Class<?> clazzs[] = cons[i].getParameterTypes();
System.out.println("构造函数["+i+"]:"+cons[i].toString() );
System.out.print("参数类型["+i+"]:(");
for (int j = 0; j < clazzs.length; j++) {
if (j == clazzs.length - 1)
System.out.print(clazzs[j].getName());
else
System.out.print(clazzs[j].getName() + ",");
}
System.out.println(")");
System.out.println("-------------------------");
}
}
}
User{age=20, name='Jack'}
--------------------------------------------
user1:User{age=22, name='hiway'}
--------------------------------------------
user2:User{age=25, name='hiway2'}
--------------------------------------------
构造函数[0]:public com.core.reflect.construction.User()
参数类型[0]:()
-------------------------
构造函数[1]:private com.core.reflect.construction.User(int,java.lang.String)
参数类型[1]:(int,java.lang.String)
-------------------------
构造函数[2]:public com.core.reflect.construction.User(java.lang.String)
参数类型[2]:(java.lang.String)
-------------------------
Constructor类本身一些常用方法如下
| 方法名称 | 方法返回值 | 方法说明 |
|---|---|---|
| getDeclaringClass() | Class | 返回 Class 对象,该对象表示声明由此 Constructor 对象表示的构造方法的类,其实就是返回真实类型(不包含参数) |
| getGenericParameterTypes() | Type[] | 按照声明顺序返回一组 Type 对象,返回的就是 Constructor对象构造函数的形参类型。 |
| getName() | String | 以字符串形式返回此构造方法的名称。 |
| getParameterTypes() | Class<?>[] | 按照声明顺序返回一组 Class 对象,即返回Constructor 对象所表示构造方法的形参类型 |
| newInstance(Object... initargs) | T | 使用此 Constructor对象表示的构造函数来创建新实例 |
| toGenericString() | String | 返回描述此 Constructor 的字符串,其中包括类型参数。 |
代码演示如下:
package com.core.reflect.construction;
import java.lang.reflect.Constructor;
import java.lang.reflect.Type;
public class Test1 {
public static void main(String[] args) throws NoSuchMethodException {
Class<User> clazz = User.class;
Constructor cs3 = clazz.getDeclaredConstructor(int.class, String.class);
System.out.println("-----getDeclaringClass-----");
Class uclazz = cs3.getDeclaringClass();
//Constructor对象表示的构造方法的类
System.out.println("构造方法的类:" + uclazz.getName());
System.out.println("-----getGenericParameterTypes-----");
//对象表示此 Constructor 对象所表示的方法的形参类型
Type[] tps = cs3.getGenericParameterTypes();
for (Type tp : tps) {
System.out.println("参数名称tp:" + tp);
}
System.out.println("-----getParameterTypes-----");
//获取构造函数参数类型
Class<?> clazzs[] = cs3.getParameterTypes();
for (Class claz : clazzs) {
System.out.println("参数名称:" + claz.getName());
}
System.out.println("-----getName-----");
//以字符串形式返回此构造方法的名称
System.out.println("getName:" + cs3.getName());
System.out.println("-----getoGenericString-----");
//返回描述此 Constructor 的字符串,其中包括类型参数。
System.out.println("getoGenericString():" + cs3.toGenericString());
}
}
-----getDeclaringClass-----
构造方法的类:com.core.reflect.construction.User
-----getGenericParameterTypes-----
参数名称tp:int
参数名称tp:class java.lang.String
-----getParameterTypes-----
参数名称:int
参数名称:java.lang.String
-----getName-----
getName:com.core.reflect.construction.User
-----getoGenericString-----
getoGenericString():private com.core.reflect.construction.User(int,java.lang.String)
Field类及其用法
Field 提供有关类或接口的单个字段的信息,以及对它的动态访问权限。反射的字段可能是一个类(静态)字段或实例字段。
同样的道理,我们可以通过Class类的提供的方法来获取代表字段信息的Field对象,Class类与Field对象相关方法如下:
| 方法名称 | 方法返回值 | 方法说明 |
|---|---|---|
| getDeclaredField(String name) | Field | 获取指定name名称的(包含private修饰的)字段,不包括继承的字段 |
| getDeclaredFields() | Field[] | 获取Class对象所表示的类或接口的所有(包含private修饰的)字段,不包括继承的字段 |
| getField(String name) | Field | 获取指定name名称、具有public修饰的字段,包含继承字段 |
| getFields() | Field[] | 获取修饰符为public的字段,包含继承字段 |
下面的代码演示了上述方法的使用过程
package com.core.reflect.field;
import java.lang.reflect.Field;
public class ReflectField {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class<?> clazz = Class.forName("com.core.reflect.field.Student");
//获取指定字段名称的Field类,注意字段修饰符必须为public而且存在该字段,
// 否则抛NoSuchFieldException
Field field = clazz.getField("age");
System.out.println("field:" + field);
//获取所有修饰符为public的字段,包含父类字段,注意修饰符为public才会获取
Field fields[] = clazz.getFields();
for (Field f : fields) {
System.out.println("f:" + f.getDeclaringClass());
}
System.out.println("================getDeclaredFields====================");
//获取当前类所字段(包含private字段),注意不包含父类的字段
Field fields2[] = clazz.getDeclaredFields();
for (Field f : fields2) {
System.out.println("f2:" + f.getDeclaringClass());
}
//获取指定字段名称的Field类,可以是任意修饰符的自动,注意不包含父类的字段
Field field2 = clazz.getDeclaredField("desc");
System.out.println("field2:" + field2);
}
//输出结果:
//field:public int com.core.reflect.field.Person.age
//f:class com.core.reflect.field.Student
//f:class com.core.reflect.field.Person
//f:class com.core.reflect.field.Person
//================getDeclaredFields====================
//f2:class com.core.reflect.field.Student
//f2:class com.core.reflect.field.Student
//field2:public java.lang.String com.core.reflect.field.Student.desc
}
class Person {
public int age;
public String name;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
class Student extends Person {
public String desc;
private int score;
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
}
上述方法需要注意的是,如果我们不期望获取其父类的字段,则需使用Class类的getDeclaredField/getDeclaredFields方法来获取字段即可,倘若需要连带获取到父类的字段,那么请使用Class类的getField/getFields,但是也只能获取到public修饰的的字段,无法获取父类的私有字段。
下面将通过Field类本身的方法对指定类属性赋值,代码演示如下:
//获取Class对象引用
Class<?> clazz = Class.forName("com.core.reflect.field..Student");
Student st= (Student) clazz.newInstance();
//获取父类public字段并赋值
Field ageField = clazz.getField("age");
ageField.set(st,18);
Field nameField = clazz.getField("name");
nameField.set(st,"Lily");
//只获取当前类的字段,不获取父类的字段
Field descField = clazz.getDeclaredField("desc");
descField.set(st,"I am student");
Field scoreField = clazz.getDeclaredField("score");
//设置可访问,score是private的
scoreField.setAccessible(true);
scoreField.set(st,88);
System.out.println(st.toString());
//输出结果:Student{age=18, name='Lily ,desc='I am student', score=88}
//获取字段值
System.out.println(scoreField.get(st));
// 88
其中的set(Object obj, Object value)方法是Field类本身的方法,用于设置字段的值,而get(Object obj)则是获取字段的值,当然关于Field类还有其他常用的方法如下:
| 方法名称 | 方法返回值 | 方法说明 |
|---|---|---|
| set(Object obj, Object value) | void | 将指定对象变量上此 Field 对象表示的字段设置为指定的新值。 |
| get(Object obj) | Object | 返回指定对象上此 Field 表示的字段的值 |
| getType() | Class<?> | 返回一个 Class 对象,它标识了此Field 对象所表示字段的声明类型。 |
| isEnumConstant() | boolean | 如果此字段表示枚举类型的元素则返回 true;否则返回 false |
| toGenericString() | String | 返回一个描述此 Field(包括其一般类型)的字符串 |
| getName() | String | 返回此 Field 对象表示的字段的名称 |
| getDeclaringClass() | Class<?> | 返回表示类或接口的 Class 对象,该类或接口声明由此 Field 对象表示的字段 |
| setAccessible(boolean flag) | void | 将此对象的 accessible 标志设置为指示的布尔值,即设置其可访问性 |
上述方法可能是较为常用的,事实上在设置值的方法上,Field类还提供了专门针对基本数据类型的方法,如setInt()/getInt()、setBoolean()/getBoolean、setChar()/getChar()等等方法,这里就不全部列出了,需要时查API文档即可。需要特别注意的是被final关键字修饰的Field字段是安全的,在运行时可以接收任何修改,但最终其实际值是不会发生改变的。
Method类及其用法
Method 提供关于类或接口上单独某个方法(以及如何访问该方法)的信息,所反映的方法可能是类方法或实例方法(包括抽象方法)。
下面是Class类获取Method对象相关的方法:
| 方法名称 | 方法返回值 | 方法说明 |
|---|---|---|
| getDeclaredMethod(String name, Class<?>... parameterTypes) | Method | 返回一个指定参数的Method对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法。 |
| getDeclaredMethods() | Method[] | 返回 Method 对象的一个数组,这些对象反映此 Class 对象表示的类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法。 |
| getMethod(String name, Class<?>... parameterTypes) | Method | 返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法。 |
| getMethods() | Method[] | 返回一个包含某些 Method 对象的数组,这些对象反映此 Class 对象所表示的类或接口(包括那些由该类或接口声明的以及从超类和超接口继承的那些的类或接口)的公共 member 方法。 |
同样通过案例演示上述方法:
package com.core.reflect.method;
import java.lang.reflect.Method;
public class ReflectMethod {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
Class clazz = Class.forName("com.core.reflect.method.Circle");
//根据参数获取public的Method,包含继承自父类的方法
Method method = clazz.getMethod("draw", int.class, String.class);
System.out.println("method:" + method);
//获取所有public的方法:
Method[] methods = clazz.getMethods();
for (Method m : methods) {
System.out.println("m::" + m);
}
System.out.println("=========================================");
//获取当前类的方法包含private,该方法无法获取继承自父类的method
Method method1 = clazz.getDeclaredMethod("drawCircle");
System.out.println("method1::" + method1);
//获取当前类的所有方法包含private,该方法无法获取继承自父类的method
Method[] methods1 = clazz.getDeclaredMethods();
for (Method m : methods1) {
System.out.println("m1::" + m);
}
}
}
class Shape {
public void draw() {
System.out.println("draw");
}
public void draw(int count, String name) {
System.out.println("draw " + name + ",count=" + count);
}
}
class Circle extends Shape {
private void drawCircle() {
System.out.println("drawCircle");
}
public int getAllCount() {
return 100;
}
}
method:public void com.core.reflect.method.Shape.draw(int,java.lang.String)
m::public int com.core.reflect.method.Circle.getAllCount()
m::public void com.core.reflect.method.Shape.draw()
m::public void com.core.reflect.method.Shape.draw(int,java.lang.String)
m::public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
m::public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
m::public final void java.lang.Object.wait() throws java.lang.InterruptedException
m::public boolean java.lang.Object.equals(java.lang.Object)
m::public java.lang.String java.lang.Object.toString()
m::public native int java.lang.Object.hashCode()
m::public final native java.lang.Class java.lang.Object.getClass()
m::public final native void java.lang.Object.notify()
m::public final native void java.lang.Object.notifyAll()
=========================================
method1::private void com.core.reflect.method.Circle.drawCircle()
m1::private void com.core.reflect.method.Circle.drawCircle()
m1::public int com.core.reflect.method.Circle.getAllCount()
在通过getMethods方法获取Method对象时,会把父类的方法也获取到,如上的输出结果,把Object类的方法都打印出来了。而getDeclaredMethod/getDeclaredMethods方法都只能获取当前类的方法。我们在使用时根据情况选择即可。下面将演示通过Method对象调用指定类的方法:
Class clazz = Class.forName("com.core.reflect.method.Circle");
//创建对象
Circle circle = (Circle) clazz.newInstance();
//获取指定参数的方法对象Method
Method method = clazz.getMethod("draw",int.class,String.class);
//通过Method对象的invoke(Object obj,Object... args)方法调用
method.invoke(circle,15,"圈圈");
//对私有无参方法的操作
Method method1 = clazz.getDeclaredMethod("drawCircle");
//修改私有方法的访问标识
method1.setAccessible(true);
method1.invoke(circle);
//对有返回值得方法操作
Method method2 =clazz.getDeclaredMethod("getAllCount");
Integer count = (Integer) method2.invoke(circle);
System.out.println("count:"+count);
draw 圈圈,count=15
drawCircle
count:100
在上述代码中调用方法,使用了Method类的invoke(Object obj,Object... args)第一个参数代表调用的对象,第二个参数传递的调用方法的参数。这样就完成了类方法的动态调用。
| 方法名称 | 方法返回值 | 方法说明 |
|---|---|---|
| invoke(Object obj, Object... args) | Object | 对带有指定参数的指定对象调用由此 Method 对象表示的底层方法。 |
| getReturnType() | Class<?> | 返回一个 Class 对象,该对象描述了此 Method 对象所表示的方法的正式返回类型,即方法的返回类型 |
| getGenericReturnType() | Type | 返回表示由此 Method 对象所表示方法的正式返回类型的 Type 对象,也是方法的返回类型。 |
| getParameterTypes() | Class<?>[] | 按照声明顺序返回 Class 对象的数组,这些对象描述了此 Method 对象所表示的方法的形参类型。即返回方法的参数类型组成的数组 |
| getGenericParameterTypes() | Type[] | 按照声明顺序返回 Type 对象的数组,这些对象描述了此 Method 对象所表示的方法的形参类型的,也是返回方法的参数类型 |
| getName() | String | 以 String 形式返回此 Method 对象表示的方法名称,即返回方法的名称 |
| isVarArgs() | boolean | 判断方法是否带可变参数,如果将此方法声明为带有可变数量的参数,则返回 true;否则,返回 false。 |
| toGenericString() | String | 返回描述此 Method 的字符串,包括类型参数。 |
getReturnType方法/getGenericReturnType方法都是获取Method对象表示的方法的返回类型,只不过前者返回的Class类型后者返回的Type(前面已分析过),Type就是一个接口而已,在Java8中新增一个默认的方法实现,返回的就参数类型信息
public interface Type {
//1.8新增
default String getTypeName() {
return toString();
}
}
而getParameterTypes/getGenericParameterTypes也是同样的道理,都是获取Method对象所表示的方法的参数类型,其他方法与前面的Field和Constructor是类似的。
反射机制执行的流程
从反射获取类实例 Class.forName("C.a.xxx")入手;
@CallerSensitive
public static Class<?> forName(String className)
throws ClassNotFoundException {
// 先通过反射,获取调用进来的类信息,从而获取当前的 classLoader
Class<?> caller = Reflection.getCallerClass();
// 调用native方法进行获取class信息
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
forName()反射获取类信息,并没有将实现留给了java,而是交给了jvm去加载!
主要是先获取 ClassLoader, 然后调用 native 方法,获取信息,加载类则是回调 java.lang.ClassLoader.
最后,jvm又会回调 ClassLoader 进类加载!
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
// sun.misc.Launcher
public Class<?> loadClass(String var1, boolean var2) throws ClassNotFoundException {
int var3 = var1.lastIndexOf(46);
if(var3 != -1) {
SecurityManager var4 = System.getSecurityManager();
if(var4 != null) {
var4.checkPackageAccess(var1.substring(0, var3));
}
}
if(this.ucp.knownToNotExist(var1)) {
Class var5 = this.findLoadedClass(var1);
if(var5 != null) {
if(var2) {
this.resolveClass(var5);
}
return var5;
} else {
throw new ClassNotFoundException(var1);
}
} else {
return super.loadClass(var1, var2);
}
}
// java.lang.ClassLoader
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// 先获取锁
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
// 如果已经加载了的话,就不用再加载了
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
// 双亲委托加载
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
// 父类没有加载到时,再自己加载
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
protected Object getClassLoadingLock(String className) {
Object lock = this;
if (parallelLockMap != null) {
// 使用 ConcurrentHashMap来保存锁
Object newLock = new Object();
lock = parallelLockMap.putIfAbsent(className, newLock);
if (lock == null) {
lock = newLock;
}
}
return lock;
}
protected final Class<?> findLoadedClass(String name) {
if (!checkName(name))
return null;
return findLoadedClass0(name);
}
下面来看一下 newInstance() 的实现方式!
// 首先肯定是 Class.newInstance
@CallerSensitive
public T newInstance()
throws InstantiationException, IllegalAccessException
{
if (System.getSecurityManager() != null) {
checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), false);
}
// NOTE: the following code may not be strictly correct under
// the current Java memory model.
// Constructor lookup
// newInstance() 其实相当于调用类的无参构造函数,所以,首先要找到其无参构造器
if (cachedConstructor == null) {
if (this == Class.class) {
// 不允许调用 Class 的 newInstance() 方法
throw new IllegalAccessException(
"Can not call newInstance() on the Class for java.lang.Class"
);
}
try {
// 获取无参构造器
Class<?>[] empty = {};
final Constructor<T> c = getConstructor0(empty, Member.DECLARED);
// Disable accessibility checks on the constructor
// since we have to do the security check here anyway
// (the stack depth is wrong for the Constructor's
// security check to work)
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<Void>() {
public Void run() {
c.setAccessible(true);
return null;
}
});
cachedConstructor = c;
} catch (NoSuchMethodException e) {
throw (InstantiationException)
new InstantiationException(getName()).initCause(e);
}
}
Constructor<T> tmpConstructor = cachedConstructor;
// Security check (same as in java.lang.reflect.Constructor)
int modifiers = tmpConstructor.getModifiers();
if (!Reflection.quickCheckMemberAccess(this, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
if (newInstanceCallerCache != caller) {
Reflection.ensureMemberAccess(caller, this, null, modifiers);
newInstanceCallerCache = caller;
}
}
// Run constructor
try {
// 调用无参构造器
return tmpConstructor.newInstance((Object[])null);
} catch (InvocationTargetException e) {
Unsafe.getUnsafe().throwException(e.getTargetException());
// Not reached
return null;
}
}
newInstance() 主要做了三件事:
- 权限检测,如果不通过直接抛出异常;
- 查找无参构造器,并将其缓存起来;
- 调用具体方法的无参构造方法,生成实例并返回;
下面是获取构造器的过程:
private Constructor<T> getConstructor0(Class<?>[] parameterTypes, int which) throws NoSuchMethodException {
// 获取所有构造器
Constructor<T>[] constructors = privateGetDeclaredConstructors((which == Member.PUBLIC));
for (Constructor<T> constructor : constructors) {
if (arrayContentsEq(parameterTypes, constructor.getParameterTypes())) {
return getReflectionFactory().copyConstructor(constructor);
}
}
throw new NoSuchMethodException(getName() + ".<init>" + argumentTypesToString(parameterTypes));
}
getConstructor0() 为获取匹配的构造方器;分三步:
- 先获取所有的constructors, 然后通过进行参数类型比较;
- 找到匹配后,通过 ReflectionFactory copy一份constructor返回;
- 否则抛出 NoSuchMethodException;
// 获取当前类所有的构造方法,通过jvm或者缓存
// Returns an array of "root" constructors. These Constructor
// objects must NOT be propagated to the outside world, but must
// instead be copied via ReflectionFactory.copyConstructor.
private Constructor<T>[] privateGetDeclaredConstructors(boolean publicOnly) {
checkInitted();
Constructor<T>[] res;
// 调用 reflectionData(), 获取保存的信息,使用软引用保存,从而使内存不够可以回收
ReflectionData<T> rd = reflectionData();
if (rd != null) {
res = publicOnly ? rd.publicConstructors : rd.declaredConstructors;
// 存在缓存,则直接返回
if (res != null) return res;
}
// No cached value available; request value from VM
if (isInterface()) {
@SuppressWarnings("unchecked")
Constructor<T>[] temporaryRes = (Constructor<T>[]) new Constructor<?>[0];
res = temporaryRes;
} else {
// 使用native方法从jvm获取构造器
res = getDeclaredConstructors0(publicOnly);
}
if (rd != null) {
// 最后,将从jvm中读取的内容,存入缓存
if (publicOnly) {
rd.publicConstructors = res;
} else {
rd.declaredConstructors = res;
}
}
return res;
}
// Lazily create and cache ReflectionData
private ReflectionData<T> reflectionData() {
SoftReference<ReflectionData<T>> reflectionData = this.reflectionData;
int classRedefinedCount = this.classRedefinedCount;
ReflectionData<T> rd;
if (useCaches &&
reflectionData != null &&
(rd = reflectionData.get()) != null &&
rd.redefinedCount == classRedefinedCount) {
return rd;
}
// else no SoftReference or cleared SoftReference or stale ReflectionData
// -> create and replace new instance
return newReflectionData(reflectionData, classRedefinedCount);
}
// 新创建缓存,保存反射信息
private ReflectionData<T> newReflectionData(SoftReference<ReflectionData<T>> oldReflectionData,int classRedefinedCount) {
if (!useCaches) return null;
// 使用cas保证更新的线程安全性,所以反射是保证线程安全的
while (true) {
ReflectionData<T> rd = new ReflectionData<>(classRedefinedCount);
// try to CAS it...
if (Atomic.casReflectionData(this, oldReflectionData, new SoftReference<>(rd))) {
return rd;
}
// 先使用CAS更新,如果更新成功,则立即返回,否则测查当前已被其他线程更新的情况,如果和自己想要更新的状态一致,则也算是成功了
oldReflectionData = this.reflectionData;
classRedefinedCount = this.classRedefinedCount;
if (oldReflectionData != null &&
(rd = oldReflectionData.get()) != null &&
rd.redefinedCount == classRedefinedCount) {
return rd;
}
}
}
如上,privateGetDeclaredConstructors(), 获取所有的构造器主要步骤;
- 先尝试从缓存中获取;
- 如果缓存没有,则从jvm中重新获取,并存入缓存,缓存使用软引用进行保存,保证内存可用;
另外,使用 relactionData() 进行缓存保存;ReflectionData 的数据结构如下!
// reflection data that might get invalidated when JVM TI RedefineClasses() is called
private static class ReflectionData<T> {
volatile Field[] declaredFields;
volatile Field[] publicFields;
volatile Method[] declaredMethods;
volatile Method[] publicMethods;
volatile Constructor<T>[] declaredConstructors;
volatile Constructor<T>[] publicConstructors;
// Intermediate results for getFields and getMethods
volatile Field[] declaredPublicFields;
volatile Method[] declaredPublicMethods;
volatile Class<?>[] interfaces;
// Value of classRedefinedCount when we created this ReflectionData instance
final int redefinedCount;
ReflectionData(int redefinedCount) {
this.redefinedCount = redefinedCount;
}
}
其中,还有一个点,就是如何比较构造是否是要查找构造器,其实就是比较类型完成相等就完了,有一个不相等则返回false。
private static boolean arrayContentsEq(Object[] a1, Object[] a2) {
if (a1 == null) {
return a2 == null || a2.length == 0;
}
if (a2 == null) {
return a1.length == 0;
}
if (a1.length != a2.length) {
return false;
}
for (int i = 0; i < a1.length; i++) {
if (a1[i] != a2[i]) {
return false;
}
}
return true;
}
// sun.reflect.ReflectionFactory
/** Makes a copy of the passed constructor. The returned
constructor is a "child" of the passed one; see the comments
in Constructor.java for details. */
public <T> Constructor<T> copyConstructor(Constructor<T> arg) {
return langReflectAccess().copyConstructor(arg);
}
// java.lang.reflect.Constructor, copy 其实就是新new一个 Constructor 出来
Constructor<T> copy() {
// This routine enables sharing of ConstructorAccessor objects
// among Constructor objects which refer to the same underlying
// method in the VM. (All of this contortion is only necessary
// because of the "accessibility" bit in AccessibleObject,
// which implicitly requires that new java.lang.reflect
// objects be fabricated for each reflective call on Class
// objects.)
if (this.root != null)
throw new IllegalArgumentException("Can not copy a non-root Constructor");
Constructor<T> res = new Constructor<>(clazz,
parameterTypes,
exceptionTypes, modifiers, slot,
signature,
annotations,
parameterAnnotations);
// root 指向当前 constructor
res.root = this;
// Might as well eagerly propagate this if already present
res.constructorAccessor = constructorAccessor;
return res;
}
通过上面,获取到 Constructor 了!
接下来就只需调用其相应构造器的 newInstance(),即返回实例了!
// return tmpConstructor.newInstance((Object[])null);
// java.lang.reflect.Constructor
@CallerSensitive
public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, null, modifiers);
}
}
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
ConstructorAccessor ca = constructorAccessor; // read volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(initargs);
return inst;
}
// sun.reflect.DelegatingConstructorAccessorImpl
public Object newInstance(Object[] args)
throws InstantiationException,
IllegalArgumentException,
InvocationTargetException
{
return delegate.newInstance(args);
}
// sun.reflect.NativeConstructorAccessorImpl
public Object newInstance(Object[] args)
throws InstantiationException,
IllegalArgumentException,
InvocationTargetException
{
// We can't inflate a constructor belonging to a vm-anonymous class
// 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(c.getDeclaringClass())) {
ConstructorAccessorImpl acc = (ConstructorAccessorImpl)
new MethodAccessorGenerator().
generateConstructor(c.getDeclaringClass(),
c.getParameterTypes(),
c.getExceptionTypes(),
c.getModifiers());
parent.setDelegate(acc);
}
// 调用native方法,进行调用 constructor
return newInstance0(c, args);
}
返回构造器的实例后,可以根据外部进行进行类型转换,从而使用接口或方法进行调用实例功能了。
- 反射获取方法 c.class.getDeclaredMethod();Method.invoke() 反射调用方法!
第一步,先获取 Method;
// java.lang.Class
@CallerSensitive
public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
throws NoSuchMethodException, SecurityException {
checkMemberAccess(Member.DECLARED, Reflection.getCallerClass(), true);
Method method = searchMethods(privateGetDeclaredMethods(false), name, parameterTypes);
if (method == null) {
throw new NoSuchMethodException(getName() + "." + name + argumentTypesToString(parameterTypes));
}
return method;
}
忽略第一个检查权限,剩下就只有两个动作了!
- 获取所有方法列表;
- 根据方法名称和方法列表,选出符合要求的方法;
- 如果没有找到相应方法,抛出异常,否则返回对应方法;
// Returns an array of "root" methods. These Method objects must NOT
// be propagated to the outside world, but must instead be copied
// via ReflectionFactory.copyMethod.
private Method[] privateGetDeclaredMethods(boolean publicOnly) {
checkInitted();
Method[] res;
ReflectionData<T> rd = reflectionData();
if (rd != null) {
res = publicOnly ? rd.declaredPublicMethods : rd.declaredMethods;
if (res != null) return res;
}
// No cached value available; request value from VM
res = Reflection.filterMethods(this, getDeclaredMethods0(publicOnly));
if (rd != null) {
if (publicOnly) {
rd.declaredPublicMethods = res;
} else {
rd.declaredMethods = res;
}
}
return res;
}
很相似,和获取所有构造器的方法很相似,都是先从缓存中获取方法,如果没有,则从jvm中获取!
不同的是,方法列表需要进行过滤 Reflection.filterMethods;当然后面看来,这个方法我们一般不会派上用场!
// sun.misc.Reflection
public static Method[] filterMethods(Class<?> containingClass, Method[] methods) {
if (methodFilterMap == null) {
// Bootstrapping
return methods;
}
return (Method[])filter(methods, methodFilterMap.get(containingClass));
}
// 可以过滤指定的方法,一般为空,如果要指定过滤,可以调用 registerMethodsToFilter(), 或者...
private static Member[] filter(Member[] members, String[] filteredNames) {
if ((filteredNames == null) || (members.length == 0)) {
return members;
}
int numNewMembers = 0;
for (Member member : members) {
boolean shouldSkip = false;
for (String filteredName : filteredNames) {
if (member.getName() == filteredName) {
shouldSkip = true;
break;
}
}
if (!shouldSkip) {
++numNewMembers;
}
}
Member[] newMembers =
(Member[])Array.newInstance(members[0].getClass(), numNewMembers);
int destIdx = 0;
for (Member member : members) {
boolean shouldSkip = false;
for (String filteredName : filteredNames) {
if (member.getName() == filteredName) {
shouldSkip = true;
break;
}
}
if (!shouldSkip) {
newMembers[destIdx++] = member;
}
}
return newMembers;
}
第二步,根据方法名和参数类型过滤指定方法返回:
private static Method searchMethods(Method[] methods,
String name,
Class<?>[] parameterTypes)
{
Method res = null;
// 使用常量池,避免重复创建String
String internedName = name.intern();
for (int i = 0; i < methods.length; i++) {
Method m = methods[i];
if (m.getName() == internedName
&& arrayContentsEq(parameterTypes, m.getParameterTypes())
&& (res == null
|| res.getReturnType().isAssignableFrom(m.getReturnType())))
res = m;
}
return (res == null ? res : getReflectionFactory().copyMethod(res));
}
大概意思看得明白,就是匹配到方法名,然后参数类型匹配,才可以!
但是,可以看到,匹配到一个方法,并没有退出for循环,而是继续进行匹配!
这里,是匹配最精确的子类进行返回(最优匹配)
最后,还是通过 ReflectionFactory, copy 方法后返回!
第三步,调用 method.invoke() 方法!
@CallerSensitive
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, obj, modifiers);
}
}
MethodAccessor ma = methodAccessor; // read volatile
if (ma == null) {
ma = acquireMethodAccessor();
}
return ma.invoke(obj, args);
}
invoke时,是通过 MethodAccessor 进行调用的,而 MethodAccessor 是个接口,在第一次时调用 acquireMethodAccessor() 进行新创建!
// probably make the implementation more scalable.
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,否则调用 ReflectionFactory 创建新的 MethodAccessor
methodAccessor = tmp;
} else {
// Otherwise fabricate one and propagate it up to the root
tmp = reflectionFactory.newMethodAccessor(this);
setMethodAccessor(tmp);
}
return tmp;
}
// sun.reflect.ReflectionFactory
public MethodAccessor newMethodAccessor(Method method) {
checkInitted();
if (noInflation && !ReflectUtil.isVMAnonymousClass(method.getDeclaringClass())) {
return new MethodAccessorGenerator().
generateMethod(method.getDeclaringClass(),
method.getName(),
method.getParameterTypes(),
method.getReturnType(),
method.getExceptionTypes(),
method.getModifiers());
} else {
NativeMethodAccessorImpl acc =
new NativeMethodAccessorImpl(method);
DelegatingMethodAccessorImpl res =
new DelegatingMethodAccessorImpl(acc);
acc.setParent(res);
return res;
}
}
两个Accessor详情:
// NativeMethodAccessorImpl / DelegatingMethodAccessorImpl
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);
}
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;
}
}
进行 ma.invoke(obj, args); 调用时,调用 DelegatingMethodAccessorImpl.invoke();
最后被委托到 NativeMethodAccessorImpl.invoke(), 即:
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);
}
// invoke0 是个 native 方法,由jvm进行调用业务方法!从而完成反射调用功能!
return invoke0(method, obj, args);
}
其中, generateMethod() 是生成具体类的方法:
/** This routine is not thread-safe */
public MethodAccessor generateMethod(Class<?> declaringClass,
String name,
Class<?>[] parameterTypes,
Class<?> returnType,
Class<?>[] checkedExceptions,
int modifiers)
{
return (MethodAccessor) generate(declaringClass,
name,
parameterTypes,
returnType,
checkedExceptions,
modifiers,
false,
false,
null);
}
/** This routine is not thread-safe */
private MagicAccessorImpl generate(final Class<?> declaringClass,
String name,
Class<?>[] parameterTypes,
Class<?> returnType,
Class<?>[] checkedExceptions,
int modifiers,
boolean isConstructor,
boolean forSerialization,
Class<?> serializationTargetClass)
{
ByteVector vec = ByteVectorFactory.create();
asm = new ClassFileAssembler(vec);
this.declaringClass = declaringClass;
this.parameterTypes = parameterTypes;
this.returnType = returnType;
this.modifiers = modifiers;
this.isConstructor = isConstructor;
this.forSerialization = forSerialization;
asm.emitMagicAndVersion();
// Constant pool entries:
// ( * = Boxing information: optional)
// (+ = Shared entries provided by AccessorGenerator)
// (^ = Only present if generating SerializationConstructorAccessor)
// [UTF-8] [This class's name]
// [CONSTANT_Class_info] for above
// [UTF-8] "sun/reflect/{MethodAccessorImpl,ConstructorAccessorImpl,SerializationConstructorAccessorImpl}"
// [CONSTANT_Class_info] for above
// [UTF-8] [Target class's name]
// [CONSTANT_Class_info] for above
// ^ [UTF-8] [Serialization: Class's name in which to invoke constructor]
// ^ [CONSTANT_Class_info] for above
// [UTF-8] target method or constructor name
// [UTF-8] target method or constructor signature
// [CONSTANT_NameAndType_info] for above
// [CONSTANT_Methodref_info or CONSTANT_InterfaceMethodref_info] for target method
// [UTF-8] "invoke" or "newInstance"
// [UTF-8] invoke or newInstance descriptor
// [UTF-8] descriptor for type of non-primitive parameter 1
// [CONSTANT_Class_info] for type of non-primitive parameter 1
// ...
// [UTF-8] descriptor for type of non-primitive parameter n
// [CONSTANT_Class_info] for type of non-primitive parameter n
// + [UTF-8] "java/lang/Exception"
// + [CONSTANT_Class_info] for above
// + [UTF-8] "java/lang/ClassCastException"
// + [CONSTANT_Class_info] for above
// + [UTF-8] "java/lang/NullPointerException"
// + [CONSTANT_Class_info] for above
// + [UTF-8] "java/lang/IllegalArgumentException"
// + [CONSTANT_Class_info] for above
// + [UTF-8] "java/lang/InvocationTargetException"
// + [CONSTANT_Class_info] for above
// + [UTF-8] "<init>"
// + [UTF-8] "()V"
// + [CONSTANT_NameAndType_info] for above
// + [CONSTANT_Methodref_info] for NullPointerException's constructor
// + [CONSTANT_Methodref_info] for IllegalArgumentException's constructor
// + [UTF-8] "(Ljava/lang/String;)V"
// + [CONSTANT_NameAndType_info] for "<init>(Ljava/lang/String;)V"
// + [CONSTANT_Methodref_info] for IllegalArgumentException's constructor taking a String
// + [UTF-8] "(Ljava/lang/Throwable;)V"
// + [CONSTANT_NameAndType_info] for "<init>(Ljava/lang/Throwable;)V"
// + [CONSTANT_Methodref_info] for InvocationTargetException's constructor
// + [CONSTANT_Methodref_info] for "super()"
// + [UTF-8] "java/lang/Object"
// + [CONSTANT_Class_info] for above
// + [UTF-8] "toString"
// + [UTF-8] "()Ljava/lang/String;"
// + [CONSTANT_NameAndType_info] for "toString()Ljava/lang/String;"
// + [CONSTANT_Methodref_info] for Object's toString method
// + [UTF-8] "Code"
// + [UTF-8] "Exceptions"
// * [UTF-8] "java/lang/Boolean"
// * [CONSTANT_Class_info] for above
// * [UTF-8] "(Z)V"
// * [CONSTANT_NameAndType_info] for above
// * [CONSTANT_Methodref_info] for above
// * [UTF-8] "booleanValue"
// * [UTF-8] "()Z"
// * [CONSTANT_NameAndType_info] for above
// * [CONSTANT_Methodref_info] for above
// * [UTF-8] "java/lang/Byte"
// * [CONSTANT_Class_info] for above
// * [UTF-8] "(B)V"
// * [CONSTANT_NameAndType_info] for above
// * [CONSTANT_Methodref_info] for above
// * [UTF-8] "byteValue"
// * [UTF-8] "()B"
// * [CONSTANT_NameAndType_info] for above
// * [CONSTANT_Methodref_info] for above
// * [UTF-8] "java/lang/Character"
// * [CONSTANT_Class_info] for above
// * [UTF-8] "(C)V"
// * [CONSTANT_NameAndType_info] for above
// * [CONSTANT_Methodref_info] for above
// * [UTF-8] "charValue"
// * [UTF-8] "()C"
// * [CONSTANT_NameAndType_info] for above
// * [CONSTANT_Methodref_info] for above
// * [UTF-8] "java/lang/Double"
// * [CONSTANT_Class_info] for above
// * [UTF-8] "(D)V"
// * [CONSTANT_NameAndType_info] for above
// * [CONSTANT_Methodref_info] for above
// * [UTF-8] "doubleValue"
// * [UTF-8] "()D"
// * [CONSTANT_NameAndType_info] for above
// * [CONSTANT_Methodref_info] for above
// * [UTF-8] "java/lang/Float"
// * [CONSTANT_Class_info] for above
// * [UTF-8] "(F)V"
// * [CONSTANT_NameAndType_info] for above
// * [CONSTANT_Methodref_info] for above
// * [UTF-8] "floatValue"
// * [UTF-8] "()F"
// * [CONSTANT_NameAndType_info] for above
// * [CONSTANT_Methodref_info] for above
// * [UTF-8] "java/lang/Integer"
// * [CONSTANT_Class_info] for above
// * [UTF-8] "(I)V"
// * [CONSTANT_NameAndType_info] for above
// * [CONSTANT_Methodref_info] for above
// * [UTF-8] "intValue"
// * [UTF-8] "()I"
// * [CONSTANT_NameAndType_info] for above
// * [CONSTANT_Methodref_info] for above
// * [UTF-8] "java/lang/Long"
// * [CONSTANT_Class_info] for above
// * [UTF-8] "(J)V"
// * [CONSTANT_NameAndType_info] for above
// * [CONSTANT_Methodref_info] for above
// * [UTF-8] "longValue"
// * [UTF-8] "()J"
// * [CONSTANT_NameAndType_info] for above
// * [CONSTANT_Methodref_info] for above
// * [UTF-8] "java/lang/Short"
// * [CONSTANT_Class_info] for above
// * [UTF-8] "(S)V"
// * [CONSTANT_NameAndType_info] for above
// * [CONSTANT_Methodref_info] for above
// * [UTF-8] "shortValue"
// * [UTF-8] "()S"
// * [CONSTANT_NameAndType_info] for above
// * [CONSTANT_Methodref_info] for above
short numCPEntries = NUM_BASE_CPOOL_ENTRIES + NUM_COMMON_CPOOL_ENTRIES;
boolean usesPrimitives = usesPrimitiveTypes();
if (usesPrimitives) {
numCPEntries += NUM_BOXING_CPOOL_ENTRIES;
}
if (forSerialization) {
numCPEntries += NUM_SERIALIZATION_CPOOL_ENTRIES;
}
// Add in variable-length number of entries to be able to describe
// non-primitive parameter types and checked exceptions.
numCPEntries += (short) (2 * numNonPrimitiveParameterTypes());
asm.emitShort(add(numCPEntries, S1));
final String generatedName = generateName(isConstructor, forSerialization);
asm.emitConstantPoolUTF8(generatedName);
asm.emitConstantPoolClass(asm.cpi());
thisClass = asm.cpi();
if (isConstructor) {
if (forSerialization) {
asm.emitConstantPoolUTF8
("sun/reflect/SerializationConstructorAccessorImpl");
} else {
asm.emitConstantPoolUTF8("sun/reflect/ConstructorAccessorImpl");
}
} else {
asm.emitConstantPoolUTF8("sun/reflect/MethodAccessorImpl");
}
asm.emitConstantPoolClass(asm.cpi());
superClass = asm.cpi();
asm.emitConstantPoolUTF8(getClassName(declaringClass, false));
asm.emitConstantPoolClass(asm.cpi());
targetClass = asm.cpi();
short serializationTargetClassIdx = (short) 0;
if (forSerialization) {
asm.emitConstantPoolUTF8(getClassName(serializationTargetClass, false));
asm.emitConstantPoolClass(asm.cpi());
serializationTargetClassIdx = asm.cpi();
}
asm.emitConstantPoolUTF8(name);
asm.emitConstantPoolUTF8(buildInternalSignature());
asm.emitConstantPoolNameAndType(sub(asm.cpi(), S1), asm.cpi());
if (isInterface()) {
asm.emitConstantPoolInterfaceMethodref(targetClass, asm.cpi());
} else {
if (forSerialization) {
asm.emitConstantPoolMethodref(serializationTargetClassIdx, asm.cpi());
} else {
asm.emitConstantPoolMethodref(targetClass, asm.cpi());
}
}
targetMethodRef = asm.cpi();
if (isConstructor) {
asm.emitConstantPoolUTF8("newInstance");
} else {
asm.emitConstantPoolUTF8("invoke");
}
invokeIdx = asm.cpi();
if (isConstructor) {
asm.emitConstantPoolUTF8("([Ljava/lang/Object;)Ljava/lang/Object;");
} else {
asm.emitConstantPoolUTF8
("(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;");
}
invokeDescriptorIdx = asm.cpi();
// Output class information for non-primitive parameter types
nonPrimitiveParametersBaseIdx = add(asm.cpi(), S2);
for (int i = 0; i < parameterTypes.length; i++) {
Class<?> c = parameterTypes[i];
if (!isPrimitive(c)) {
asm.emitConstantPoolUTF8(getClassName(c, false));
asm.emitConstantPoolClass(asm.cpi());
}
}
// Entries common to FieldAccessor, MethodAccessor and ConstructorAccessor
emitCommonConstantPoolEntries();
// Boxing entries
if (usesPrimitives) {
emitBoxingContantPoolEntries();
}
if (asm.cpi() != numCPEntries) {
throw new InternalError("Adjust this code (cpi = " + asm.cpi() +
", numCPEntries = " + numCPEntries + ")");
}
// Access flags
asm.emitShort(ACC_PUBLIC);
// This class
asm.emitShort(thisClass);
// Superclass
asm.emitShort(superClass);
// Interfaces count and interfaces
asm.emitShort(S0);
// Fields count and fields
asm.emitShort(S0);
// Methods count and methods
asm.emitShort(NUM_METHODS);
emitConstructor();
emitInvoke();
// Additional attributes (none)
asm.emitShort(S0);
// Load class
vec.trim();
final byte[] bytes = vec.getData();
// Note: the class loader is the only thing that really matters
// here -- it's important to get the generated code into the
// same namespace as the target class. Since the generated code
// is privileged anyway, the protection domain probably doesn't
// matter.
return AccessController.doPrivileged(
new PrivilegedAction<MagicAccessorImpl>() {
public MagicAccessorImpl run() {
try {
return (MagicAccessorImpl)
ClassDefiner.defineClass
(generatedName,
bytes,
0,
bytes.length,
declaringClass.getClassLoader()).newInstance();
} catch (InstantiationException | IllegalAccessException e) {
throw new InternalError(e);
}
}
});
}
主要看这一句:ClassDefiner.defineClass(xx, declaringClass.getClassLoader()).newInstance();
在ClassDefiner.defineClass方法实现中,每被调用一次都会生成一个DelegatingClassLoader类加载器对象 ,这里每次都生成新的类加载器,是为了性能考虑,在某些情况下可以卸载这些生成的类,因为类的卸载是只有在类加载器可以被回收的情况下才会被回收的,如果用了原来的类加载器,那可能导致这些新创建的类一直无法被卸载!
而反射生成的类,有时候可能用了就可以卸载了,所以使用其独立的类加载器,从而使得更容易控制反射类的生命周期!
最后,用几句话总结反射的实现原理:
- 反射类及反射方法的获取,都是通过从列表中搜寻查找匹配的方法,所以查找性能会随类的大小方法多少而变化;
- 每个类都会有一个与之对应的Class实例,从而每个类都可以获取method反射方法,并作用到其他实例身上;
- 反射也是考虑了线程安全的,放心使用;
- 反射使用软引用relectionData缓存class信息,避免每次重新从jvm获取带来的开销;
- 反射调用多次生成新代理Accessor, 而通过字节码生存的则考虑了卸载功能,所以会使用独立的类加载器;
- 当找到需要的方法,都会copy一份出来,而不是使用原来的实例,从而保证数据隔离;
- 调度反射方法,最终是由jvm执行invoke0()执行;
反射调用的DEMO
首先,总结下方法的反射调用,也就是 Method.invoke,是怎么实现的。
public final class Method extends Executable {
...
public Object invoke(Object obj, Object... args) throws ... {
... // 权限检查
MethodAccessor ma = methodAccessor;
if (ma == null) {
ma = acquireMethodAccessor();
}
return ma.invoke(obj, args);
}
}
实际上委派给 MethodAccessor 来处理。MethodAccessor 是一个接口,它有两个已有的具体实现:一个通过本地方法来实现反射调用,另一个则使用了委派模式。为了方便记忆,用“本地实现”和“委派实现”来指代这两者。
每个 Method 实例的第一次反射调用都会生成一个委派实现,它所委派的具体实现便是一个本地实现。本地实现非常容易理解。当进入了 Java 虚拟机内部之后,我们便拥有了 Method 实例所指向方法的具体地址。这时候,反射调用无非就是将传入的参数准备好,然后调用进入目标方法。
// v0 版本
import java.lang.reflect.Method;
public class Test {
public static void target(int i) {
new Exception("#" + i).printStackTrace();
}
public static void main(String[] args) throws Exception {
Class<?> klass = Class.forName("Test");
Method method = klass.getMethod("target", int.class);
method.invoke(null, 0);
}
}
# 不同版本的输出略有不同,这里我使用了 Java 10。
$ java Test
java.lang.Exception: #0
at Test.target(Test.java:5)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl .invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl. .invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.i .invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:564)
at Test.main(Test.java:131
为了方便理解,我们可以打印一下反射调用到目标方法时的栈轨迹。在上面的 v0 版本代码中,我们获取了一个指向 Test.target 方法的 Method 对象,并且用它来进行反射调用。在 Test.target 中,我会打印出栈轨迹。
可以看到,反射调用先是调用了 Method.invoke,然后进入委派实现(DelegatingMethodAccessorImpl),再然后进入本地实现(NativeMethodAccessorImpl),最后到达目标方法。
这里你可能会疑问,为什么反射调用还要采取委派实现作为中间层?直接交给本地实现不可以么?
其实,Java 的反射调用机制还设立了另一种动态生成字节码的实现(动态实现),直接使用 invoke 指令来调用目标方法。之所以采用委派实现,便是为了能够在本地实现以及动态实现中切换。
// 动态实现的伪代码,这里只列举了关键的调用逻辑,其实它还包括调用者检测、参数检测的字节码。
package jdk.internal.reflect;
public class GeneratedMethodAccessor1 extends ... {
@Overrides
public Object invoke(Object obj, Object[] args) throws ... {
Test.target((int) args[0]);
return null;
}
}
动态实现和本地实现相比,其运行效率要快上 20 倍 。这是因为动态实现无需经过 Java 到 C++ 再到 Java 的切换,但由于生成字节码十分耗时,仅调用一次的话,反而是本地实现要快上 3 到 4 倍 。
考虑到许多反射调用仅会执行一次,Java 虚拟机设置了一个阈值 15(可以通过 -Dsun.reflect.inflationThreshold= 来调整),当某个反射调用的调用次数在 15 之下时,采用本地实现;当达到 15 时,便开始动态生成字节码,并将委派实现的委派对象切换至动态实现,这个过程我们称之为 Inflation。
为了观察这个过程,我将刚才的例子更改为下面的 v1 版本。它会将反射调用循环 20 次。
// v1 版本
import java.lang.reflect.Method;
public class Test {
public static void target(int i) {
new Exception("#" + i).printStackTrace();
}
public static void main(String[] args) throws Exception {
Class<?> klass = Class.forName("Test");
Method method = klass.getMethod("target", int.class);
// 循环20次
for (int i = 0; i < 20; i++) {
method.invoke(null, i);
}
}
}
# 使用 -verbose:class 打印加载的类
$ java -verbose:class Test
...
java.lang.Exception: #14
at Test.target(Test.java:5)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl .invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl .invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl .invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:564)
at Test.main(Test.java:12)
[0.158s][info][class,load] ...
...
[0.160s][info][class,load] jdk.internal.reflect.GeneratedMethodAccessor1 source: __JVM_DefineClass__
java.lang.Exception: #15
at Test.target(Test.java:5)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl .invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl .invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl .invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:564)
at Test.main(Test.java:12)
java.lang.Exception: #16
at Test.target(Test.java:5)
at jdk.internal.reflect.GeneratedMethodAccessor1 .invoke(Unknown Source)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl .invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:564)
at Test.main(Test.java:12)
可以看到,在第 15 次(从 0 开始数)反射调用时,我们便触发了动态实现的生成。这时候,Java 虚拟机额外加载了不少类。其中,最重要的当属 GeneratedMethodAccessor1(第 30 行)。并且,从第 16 次反射调用开始,我们便切换至这个刚刚生成的动态实现(第 40 行)。
反射调用的 Inflation 机制是可以通过参数(-Dsun.reflect.noInflation=true)来关闭的。这样一来,在反射调用一开始便会直接生成动态实现,而不会使用委派实现或者本地实现。
反射调用的开销
下面,来拆解下反射调用的性能开销。
在刚才的例子中,我们先后进行了 Class.forName,Class.getMethod 以及 Method.invoke 三个操作。其中,Class.forName 会调用本地方法,Class.getMethod 则会遍历该类的公有方法。如果没有匹配到,它还将遍历父类的公有方法。可想而知,这两个操作都非常费时。
值得注意的是,以 getMethod 为代表的查找方法操作,会返回查找得到结果的一份拷贝。因此,我们应当避免在热点代码中使用返回 Method 数组的 getMethods 或者 getDeclaredMethods 方法,以减少不必要的堆空间消耗。
在实践中,我们往往会在应用程序中缓存 Class.forName 和 Class.getMethod 的结果。因此,下面只关注反射调用本身的性能开销。
为了比较直接调用和反射调用的性能差距,将前面的例子改为下面的 v2 版本。它会将反射调用循环二十亿次。此外,它还将记录下每跑一亿次的时间。然后取最后五个记录的平均值,作为预热后的峰值性能。一亿次直接调用耗费的时间大约在 120ms。这和不调用的时间是一致的。其原因在于这段代码属于热循环,同样会触发即时编译。并且,即时编译会将对 Test.target 的调用内联进来,从而消除了调用的开销。
// v2 版本
mport java.lang.reflect.Method;
public class Test {
public static void target(int i) {
// 空方法
}
public static void main(String[] args) throws Exception {
Class<?> klass = Class.forName("Test");
Method method = klass.getMethod("target", int.class);
long current = System.currentTimeMillis();
for (int i = 1; i <= 2_000_000_000; i++) {
if (i % 100_000_000 == 0) {
long temp = System.currentTimeMillis();
System.out.println(temp - current);
current = temp;
}
method.invoke(null, 128);
}
}
}
下面将以 120ms 作为基准,来比较反射调用的性能开销。
以直接调用耗时 120ms 作为基准
由于目标方法 Test.target 接收一个 int 类型的参数,因此传入 128 作为反射调用的参数,测得的结果约为基准的 2.7 倍。我们暂且不管这个数字是高是低,先来看看在反射调用之前字节码都做了什么。
59: aload_2 // 加载 Method 对象
60: aconst_null // 反射调用的第一个参数 null
61: iconst_1
62: anewarray Object // 生成一个长度为 1 的 Object 数组
65: dup
66: iconst_0
67: sipush 128
70: invokestatic Integer.valueOf // 将 128 自动装箱成 Integer
73: aastore // 存入 Object 数组中
74: invokevirtual Method.invoke // 反射调用
这里截取了循环中反射调用编译而成的字节码。可以看到,这段字节码除了反射调用外,还额外做了两个操作。
- 第一,由于 Method.invoke 是一个变长参数方法,在字节码层面它的最后一个参数会是 Object 数组。Java 编译器会在方法调用处生成一个长度为传入参数数量的 Object 数组,并将传入参数一一存储进该数组中。
- 第二,由于 Object 数组不能存储基本类型,Java 编译器会对传入的基本类型参数进行自动装箱。
这两个操作除了带来性能开销外,还可能占用堆内存,使得 GC 更加频繁。那么,如何消除这部分开销呢?
关于第二个自动装箱,Java 缓存了 [-128, 127] 中所有整数所对应的 Integer 对象。当需要自动装箱的整数在这个范围之内时,便返回缓存的 Integer,否则需要新建一个 Integer 对象。
因此,我们可以将这个缓存的范围扩大至覆盖 128(对应参数 -Djava.lang.Integer.IntegerCache.high=128),便可以避免需要新建 Integer 对象的场景。或者,我们可以在循环外缓存 128 自动装箱得到的 Integer 对象,并且直接传入反射调用中。这两种方法测得的结果差不多,约为基准的 1.8 倍。
现在我们再回来看看第一个因变长参数而自动生成的 Object 数组。既然每个反射调用对应的参数个数是固定的,那么我们可以选择在循环外新建一个 Object 数组,设置好参数,并直接交给反射调用。改好的代码可以参照文稿中的 v3 版本。
// v3 版本
import java.lang.reflect.Method;
public class Test {
public static void target(int i) {
// 空方法
}
public static void main(String[] args) throws Exception {
Class<?> klass = Class.forName("Test");
Method method = klass.getMethod("target", int.class);
Object[] arg = new Object[1]; // 在循环外构造参数数组
arg[0] = 128;
long current = System.currentTimeMillis();
for (int i = 1; i <= 2_000_000_000; i++) {
if (i % 100_000_000 == 0) {
long temp = System.currentTimeMillis();
System.out.println(temp - current);
current = temp;
}
method.invoke(null, arg);
}
}
}
测得的结果反而更糟糕了,为基准的 2.9 倍。这是为什么呢?
如果你在上一步解决了自动装箱之后查看运行时的 GC 状况,你会发现这段程序并不会触发 GC。其原因在于,原本的反射调用被内联了,从而使得即时编译器中的逃逸分析将原本新建的 Object 数组判定为不逃逸的对象。
如果一个对象不逃逸,那么即时编译器可以选择栈分配甚至是虚拟分配,也就是不占用堆空间。如果在循环外新建数组,即时编译器无法确定这个数组会不会中途被更改,因此无法优化掉访问数组的操作,可谓是得不偿失。
到目前为止,我们的最好记录是 1.8 倍。那能不能再进一步提升呢?
关闭反射调用的 Inflation 机制,从而取消委派实现,并且直接使用动态实现。此外,每次反射调用都会检查目标方法的权限,而这个检查同样可以在 Java 代码里关闭,在关闭了这两项机制之后,也就得到了我们的 v4 版本,它测得的结果约为基准的 1.3 倍。
// v4 版本
import java.lang.reflect.Method;
// 在运行指令中添加如下两个虚拟机参数:
// -Djava.lang.Integer.IntegerCache.high=128
// -Dsun.reflect.noInflation=true
public class Test {
public static void target(int i) {
// 空方法
}
public static void main(String[] args) throws Exception {
Class<?> klass = Class.forName("Test");
Method method = klass.getMethod("target", int.class);
method.setAccessible(true); // 关闭权限检查
long current = System.currentTimeMillis();
for (int i = 1; i <= 2_000_000_000; i++) {
if (i % 100_000_000 == 0) {
long temp = System.currentTimeMillis();
System.out.println(temp - current);
current = temp;
}
method.invoke(null, 128);
}
}
}
到这里,我们基本上把反射调用的水分都榨干了。
首先,在这个例子中,之所以反射调用能够变得这么快,主要是因为即时编译器中的方法内联。在关闭了 Inflation 的情况下,内联的瓶颈在于 Method.invoke 方法中对 MethodAccessor.invoke 方法的调用。
在生产环境中,我们往往拥有多个不同的反射调用,对应多个 GeneratedMethodAccessor,也就是动态实现。
由于 Java 虚拟机的关于上述调用点的类型 profile(注:对于 invokevirtual 或者 invokeinterface,Java 虚拟机会记录下调用者的具体类型,我们称之为类型 profile)无法同时记录这么多个类,因此可能造成所测试的反射调用没有被内联的情况。
// v5 版本
import java.lang.reflect.Method;
public class Test {
public static void target(int i) {
// 空方法
}
public static void main(String[] args) throws Exception {
Class<?> klass = Class.forName("Test");
Method method = klass.getMethod("target", int.class);
method.setAccessible(true); // 关闭权限检查
polluteProfile();
long current = System.currentTimeMillis();
for (int i = 1; i <= 2_000_000_000; i++) {
if (i % 100_000_000 == 0) {
long temp = System.currentTimeMillis();
System.out.println(temp - current);
current = temp;
}
method.invoke(null, 128);
}
}
public static void polluteProfile() throws Exception {
Method method1 = Test.class.getMethod("target1", int.class);
Method method2 = Test.class.getMethod("target2", int.class);
for (int i = 0; i < 2000; i++) {
method1.invoke(null, 0);
method2.invoke(null, 0);
}
}
public static void target1(int i) { }
public static void target2(int i) { }
}
在上面的 v5 版本中,我在测试循环之前调用了 polluteProfile 的方法。该方法将反射调用另外两个方法,并且循环上 2000 遍。
而测试循环则保持不变。测得的结果约为基准的 6.7 倍。也就是说,只要误扰了 Method.invoke 方法的类型 profile,性能开销便会从 1.3 倍上升至 6.7 倍。
之所以这么慢,除了没有内联之外,另外一个原因是逃逸分析不再起效。这时候,我们便可以采用刚才 v3 版本中的解决方案,在循环外构造参数数组,并直接传递给反射调用。这样子测得的结果约为基准的 5.2 倍。
除此之外,我们还可以提高 Java 虚拟机关于每个调用能够记录的类型数目(对应虚拟机参数 -XX:TypeProfileWidth,默认值为 2,这里设置为 3)。最终测得的结果约为基准的 2.8 倍,尽管它和原本的 1.3 倍还有一定的差距,但总算是比 6.7 倍好多了。
总结
在默认情况下,方法的反射调用为委派实现,委派给本地实现来进行方法调用。在调用超过 15 次之后,委派实现便会将委派对象切换至动态实现。这个动态实现的字节码是自动生成的,它将直接使用 invoke 指令来调用目标方法。
方法的反射调用会带来不少性能开销,原因主要有三个:
- 变长参数方法导致的 Object 数组
- 基本类型的自动装箱、拆箱,
- 方法内联。