深入理解Java中的反射机制和使用原理!详细解析invoke方法的执行和使用

2023-11-03

反射的概念

  • 反射:Refelection,反射是Java的特征之一,允许运行中的Java程序获取自身信息,并可以操作类或者对象的内部属性
    • 通过反射,可以在运行时获得程序或者程序中的每一个类型的成员活成成员的信息
    • 程序中的对象一般都是在编译时就确定下来,Java反射机制可以动态地创建对象并且调用相关属性,这些对象的类型在编译时是未知的
    • 也就是说 ,可以通过反射机制直接创建对象,即使这个对象类型在编译时是未知的
  • Java反射提供下列功能:
    • 在运行时判断任意一个对象所属的类
    • 在运行时构造任意一个类的对象
    • 在运行时判断任意一个类所具有的成员变量和方法,可以通过反射调用 private 方法
    • 在运行时调用任意一个对象的方法

反射的原理

  • 反射的核心: JVM在运行时才动态加载类或者调用方法以及访问属性,不需要事先(比如编译时)知道运行对象是什么
  • 类的加载:
    • Java反射机制是围绕Class类展开的
    • 首先要了解类的加载机制:
      • JVM 使用 ClassLoader 将字节码文件,即 class 文件加载到方法区内存中
Class clazz = ClassLoader.getSystemClassLoader().loadClass("com.mypackage.MyClass");

ClassLoader类根据类的完全限定名加载类并返回一个 Class 对象

  • ReflectionData:
    • 为了提高反射的性能,必须要提供缓存
    • class 类内部使用一个 useCaches 静态变量来标记是否使用缓存
    • 这个值可以通过外部的 sun.reflect.noCaches 配置是否禁用缓存
    • class 类内部提供了一个 ReflectionData 内部类用来存放反射数据的缓存,并声明了一个 reflectionData
    • 由于稍后进行按需延迟加载并缓存,所以这个域并没有指向一个实例化的 ReflectionData 对象
// 标记是否使用缓存,可以通过外部的sun.reflect.noCaches配置是否禁用缓存
private static boolean useCaches = true;

static class ReflectionData<T> {
	volatile Field[] declaredFields;
	volatile Field[] publicFields;
	volatile Method[] declaredMethods;
	volatile Method[] publicMethods;
	volatile Constructor<T>[] declaredConstructors;
	volatile Constructors<T>[] publicConstructors;
	volatile Field[] declaredPublicFields;
	volatile Method[] declaredPublicMethods;
	final int redefinedCount;

	ReflectionData(int redefinedCount) {
		this.redefinedCount = redefinedCount;
	}
}
	
	// 这个是SoftReference,在内存资源紧张的时候可能会被回收
	// volatile保证多线程环境下读写的正确性
	 private volatile transient SoftReference<RefelectionData<T>> reflectionData;

	// 这个主要用于和ReflectionData中的redefinedCount进行比较
	// 如果两个值不相等,说明ReflectionData缓存的数据已经过期了
	private volatile transient classRedefinedCount = 0;

反射的主要用途

  • 反射最重要的用途就是开发各种通用框架
    • 很多框架都是配置化的,通过 XML 文件配置 Bean
    • 为了保证框架的通用性,需要根据配置文件加载不同的对象或者类,调用不同的方法
    • 要运用反射,运行时动态加载需要加载的对象
  • 示例:
    • 在运用 Struts 2 框架的开发中会在 struts.xml 中配置 Action:
<action name="login"
               class="org.ScZyhSoft.test.action.SimpleLoginAction"
               method="execute">
           <result>/shop/shop-index.jsp</result>
           <result name="error">login.jsp</result>
       </action>
  • 配置文件与 Action 建立映射关系
  • View 层发出请求时,请求会被 StrutsPrepareAndExecuteFilter 拦截
  • StrutsPrepareAndExecuteFilter会动态地创建Action实例
    • 请求 login.action
    • StrutsPrepareAndExecuteFilter 会解析 struts.xml 文件
    • 检索 actionnameloginAction
    • 根据 class 属性创建 SimpleLoginAction 实例
    • 使用 invoke 方法调用 execute 方法
  • 反射是各种容器实现的核心

反射的运用

  • 反射相关的类在 StrutsPrepareAndExecuteFilter
  • 反射可以用于:
    • 判断对象所属的类
    • 获得class对象
    • 构造任意一个对象
    • 调用一个对象
  • 九大预定义的Class对象:
    • 基本的Java类型: boolean, byte, char, short, int, long, float, double
    • 关键字void通过class属性的也表示为Class对象
    • 包装类或者void类的静态TYPE字段:
      • Integer.TYPE == int.class
      • Integer.class == int.class
    • 数组类型的Class实例对象:
      • Class<String[]> clz = String[].class;
      • 数组的Class对象如何比较是否相等:
        • 数组的维数
        • 数组的类型
        • Class类中的 isArray() ,用来判断是否表示一个数组类型

获得Class对象

  • 使用Class类的forName静态方法:
public static Class<?> forName(String className);



/* 在JDBC中使用这个方法加载数据库驱动 */
Class.forName(driver);
  • 直接获取一个对象的class:
Class<?> klass=int.class;
Class<?> classInt=Integer.TYPE;
  • 调用对象的getClass()方法:
StringBuilder str=new StringBuilder("A");
Class<?> klass=str.getClass();

判断是否是某个类的实例

  • 一般来说,使用 instanceof 关键字判断是否为某个类的实例
  • 在反射中,可以使用 Class 对象的 isInstance() 方法来判断是否为某个类的实例,这是一个 native 方法
public native boolean isInstance(Object obj);

创建实例

通过反射生成对象的实例主要有两种方式:

  • 使用Class对象的newInstance()方法来创建Class对象对应类的实例:
Class<?> c = String.class;
Object str = c.newInstance();
  • 先通过Class对象获取指定的Constructor对象,再调用Constructor对象的newInstance()方法来创建实例: 可以用指定的构造器构造类的实例
/* 获取String所对应的Class对象 */
Class<?> c=String.class;

/* 获取String类带一个String参数的构造器 */
Constructor constructor=c.getConstructor(String.class);

/* 根据构造器创建实例 */
Object obj=constructor.newInstance("abc");
System.out.println(obj);

获取方法

获取Class对象的方法集合,主要有三种方法:

  • getDeclaredMethods():

    返回类或接口声明的所有方法:

    • 包括公共,保护,默认(包)访问和私有方法
    • 不包括继承的方法
public Method[] getDeclaredMethods() throws SecurityException {}
  • getMethods():返回某个类所有的public方法
    • 包括继承类的 public 方法
public Method[] getMethods() throws SecurityException {}
  • getMethod():

    返回一个特定的方法

    • 第一个参数 :方法名称
    • 后面的参数 :方法的参数对应Class的对象
public Method getMethod(String name,Class<?>... parameterType) {}
  • 获取方法示例:
public class MethodTest {
	public static void methodTest() throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
		Class<?> c = methodClass.class;
		Object object = c.newInstance();
		Method[] methods = c.getMethods();
		Method[] declaredMethods = c.getDeclaredMethods();
		// 获取methodClass类中的add方法
		Method method = c.getMethod("add", int.class, int.class);
		// getMethods()方法获取的所有方法
		System.out.println("getMethods获取的方法:");
		for (Method m:methods)
			System.out.println(m);
		// getDeclaredMethods()方法获取的所有方法
		System.out.println("getDeclaredMethods获取的方法:");
		for (Method m:declaredMethods)
			System.out.println(m);
	}
}

class methodClass {
	public final int n = 3;
	
	public int add(int a, int b) {
		return a + b;
	}
	
	public int sub(int a, int b) {
		return a * b;
	}
}

程序运行结果:

getMethods获取的方法:
public int org.ScZyhSoft.common.methodClass.add(int,int)
public int org.ScZyhSoft.common.methodClass.sub(int,int)
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
getDeclaredMethods获取的方法:
public int org.ScZyhSoft.common.methodClass.add(int,int)
public int org.ScZyhSoft.common.methodClass.sub(int,int)

通过 getMethods() 获取的方法可以获取到父类的方法

获取构造器信息

  • 通过 Class 类的 getConstructor 方法得到 Constructor 类的一个实例
  • Constructor 类中 newInstance 方法可以创建一个对象的实例:
public T newInstance(Objec ... initargs)

newInstance方法可以根据传入的参数来调用对应的 Constructor 创建对象的实例

获取类的成员变量信息

  • getFileds: 获取公有的成员变量
  • getDeclaredFields: 获取所有已声明的成员变量,但是不能得到父类的成员变量

调用方法

  • 从类中获取一个方法后,可以使用 invoke() 来调用这个方法
public Object invoke(Object obj, Object ... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {}
  • 示例:
public class InvokeTest {
	public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
		Class<?> klass = method.class;
		// 创建methodClass的实例
		Object obj = klass.newInstance();
		// 获取methodClass的add方法
		Method method = klass.getMethod("add", int.class, int.class);
		// 调用method对应的方法,实现add(1,4)
		Object result = method.invoke(obj, 1, 4);
		System.out.println(result);
	}
}

class methodClass {
	public final int n = 3;
	public int add(int a, int b) {
		return a + b;
	}
	public int sub(int a,int b) {
		return a * b;
	}
}

利用反射创建数组

  • 数组是Java中一种特殊的数据类型,可以赋值给一个 Object Reference
  • 利用反射创建数组的示例:
public static void ArrayTest() throws ClassNotFoundException {
	Class<?> cls = class.forName("java.lang.String");
	Object array = Array.newInstance(cls, 25);
	// 在数组中添加数据
	Array.set(array, 0, "C");
	Array.set(array, 1, "Java");
	Array.set(array, 2, "Python");
	Array.set(array, 3, "Scala");
	Array.set(array, 4, "Docker");
	// 获取数据中的某一项内容
	System.out.println(Array.get(array, 3));
}

Array类是 java.lang.reflect.Array 类,通过 Array.newInstance() 创建数组对象:

public static Object newInstance(Class<?> componentType, int length) throws NegativeArraySizeException {
	return newArray(componentType, length);
}

newArray方法是一个 native 方法,具体实现在 HotSpot JVM 中,源码如下:

private static native Object newArray(Class<?> componentType, int length) throws NegativeArraySizeException;

----------------------------------------------------------

- newArray源码目录: openjdk\hotspot\src\share\vm\runtime\reflection.cpp

arrayOop Reflection::reflect_new_array(oop element_mirror, jint length, TRAPS) {
  if (element_mirror == NULL) {
    THROW_0(vmSymbols::java_lang_NullPointerException());
  }
  if (length < 0) {
    THROW_0(vmSymbols::java_lang_NegativeArraySizeException());
  }
  if (java_lang_Class::is_primitive(element_mirror)) {
    Klass* tak = basic_type_mirror_to_arrayklass(element_mirror, CHECK_NULL);
    return TypeArrayKlass::cast(tak)->allocate(length, THREAD);
  } else {
    Klass* k = java_lang_Class::as_Klass(element_mirror);
    if (k->oop_is_array() && ArrayKlass::cast(k)->dimension() >= MAX_DIM) {
      THROW_0(vmSymbols::java_lang_IllegalArgumentException());
    }
    return oopFactory::new_objArray(k, length, THREAD);
  }
}
  • Array类的set和get方法都是native方法,具体实现在HotSpot JVM中,对应关系如下:
    • set: Reflection::array_set
    • get: Reflection::array_get

invoke方法

  • 在Java中很多方法都会调用 invoke 方法,很多异常的抛出多会定位到 invoke 方法:
java.lang.NullPointerException
  at ......
  at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
  at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  at java.lang.reflect.Method.invoke(Method.java:497)

invoke执行过程

  • invoke 方法用来在运行时动态地调用某个实例的方法,实现如下:
@CallSensitive
public Object invoke(Object obj, Object ... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
	if (!override) {
		if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
			Class<?> caller = Refelection.getCallerClass();
			checkAccess(caller, clazz, obj, modifiers);
		}
	}
	MethodAccessor ma = methodAccessor;
	if (ma == null) {
		ma = acquireMethodAccessor();
	}
	return ma.invoke(obj, args);
}
权限检查
  • AccessibleObject类是Field,Method和Constructor对象的基类:
    • 提供将反射的对象标记为在使用时取消默认Java语言访问控制检查的能力
  • invoke方法会首先检查AccessibleObject的override属性的值:
    • override默认值为false:
      • 表示需要权限调用规则,调用方法时需要检查权限
      • 也可以使用 setAccessible() 设置为 true
    • override如果值为true:
      • 表示忽略权限规则,调用方法时无需检查权限
      • 也就是说,可以调用任意private方法,违反了封装
  • 如果override属性为默认值false,则进行进一步的权限检查:
  1. 首先用 Reflection.quickCheckMemberAccess(clazz, modifiers) 方法检查方法是否为 public

    1.1 如果是public方法的话,就跳过本步

    1.2 如果不是public方法的话,就用Reflection.getCallerClass()方法获取调用这个方法的Class对象,这是一个 native 方法

@CallerSensitive
	public static native Class<?> getCallerClass();

----------------------------------------------------------


- 在OpenJDK中可以找到getCallerClass方法的JNI入口-Reflection.c

JNIEXPORT jclass JNICALL Java_sun_reflect_Reflection_getCallerClass__
(JNIEnv *env, jclass unused)
{
    return JVM_GetCallerClass(env, JVM_CALLER_DEPTH);
}

----------------------------------------------------------

- JVM_GetCallerClass的源码位于jvm.cpp中

VM_ENTRY(jclass, JVM_GetCallerClass(JNIEnv* env, int depth))
  JVMWrapper("JVM_GetCallerClass");
  // Pre-JDK 8 and early builds of JDK 8 don't have a CallerSensitive annotation; or
  // sun.reflect.Reflection.getCallerClass with a depth parameter is provided
  // temporarily for existing code to use until a replacement API is defined.
  if (SystemDictionary::reflect_CallerSensitive_klass() == NULL || depth != JVM_CALLER_DEPTH) {
    Klass* k = thread->security_get_caller_class(depth);
    return (k == NULL) ? NULL : (jclass) JNIHandles::make_local(env, k->java_mirror());
  }
  // Getting the class of the caller frame.
  //
  // The call stack at this point looks something like this:
  //
  // [0] [ @CallerSensitive public sun.reflect.Reflection.getCallerClass ]
  // [1] [ @CallerSensitive API.method                                   ]
  // [.] [ (skipped intermediate frames)                                 ]
  // [n] [ caller                                                        ]
  vframeStream vfst(thread);
  // Cf. LibraryCallKit::inline_native_Reflection_getCallerClass
  for (int n = 0; !vfst.at_end(); vfst.security_next(), n++) {
    Method* m = vfst.method();
    assert(m != NULL, "sanity");
    switch (n) {
    case 0:
      // This must only be called from Reflection.getCallerClass
      if (m->intrinsic_id() != vmIntrinsics::_getCallerClass) {
        THROW_MSG_NULL(vmSymbols::java_lang_InternalError(), "JVM_GetCallerClass must only be called from Reflection.getCallerClass");
      }
      // fall-through
    case 1:
      // Frame 0 and 1 must be caller sensitive.
      if (!m->caller_sensitive()) {
        THROW_MSG_NULL(vmSymbols::java_lang_InternalError(), err_msg("CallerSensitive annotation expected at frame %d", n));
      }
      break;
    default:
      if (!m->is_ignored_by_security_stack_walk()) {
        // We have reached the desired frame; return the holder class.
        return (jclass) JNIHandles::make_local(env, m->method_holder()->java_mirror());
      }
      break;
    }
  }
  return NULL;
JVM_END
  1. 获取 Class 对象 caller 后使用 checkAccess 方法进行一次快速的权限校验 ,checkAccess 方法实现如下:
volatile Object securityCheckCache;

	void checkAccess(Class<?> caller, Class<?> clazz, Object obj, int modifiers) throws IllegalAccessException {
		if(caller == clazz){	// 快速校验
			return;				// 权限通过校验
		}
		Object cache = securityCheckCache;	// 读取volatile
		Class<?> targetClass = clazz;
		if (obj != null && Modifier.isProtected(modifiers) && ((targetClass = obj.getClass()) != clazz)) {	// 必须匹配caller,targetClass中的一个
			if (cache instanceof Class[]) {
				Class<?>[] cache2 = (Class<?>[]) cache;
				if (cache2[1] == targetClass && cache[0] == caller) {
					return;		// 校验通过
				}
			}
		} else if (cache == caller) {
			return;				// 校验通过
		}
		slowCheckMemberAccess(caller, clazz, obj, modifiers, targetClass);
	}

首先先执行一次快速校验,一旦 Class 正确则权限检查通过;如果未通过,则创建一个缓存,中间再进行检查

  • 如果上面所有的权限检查都未通过,将会执行更详细的检查:
void slowCheckMemberAccess(Class<?> caller, Class<?> clazz, Object obj, int modifiers, Class<?> targetClass) throws IllegalAccessException {
	Refelection.ensureMemberAccess(caller, clazz, obj, modifiers);
	// 如果成功,就更新缓存
	Object cache = ((targetClass == clazz) ? caller : new Class<?>[] {caller, targetClass});
	securityCheckCache = cache;
}

Reflection.ensureMemberAccess 方法继续检查权限.若检查通过就更新缓存,这样下一次同一个类调用同一个方法时就不用执行权限检查了,这是一种简单的缓存机制

由于 JMMhappens-before 规则能够保证缓存初始化能够在写缓存之间发生,因此两个cache不需要声明为volatile

  • 检查权限的工作到此结束.如果没有通过检查就会抛出异常,如果通过检查就会到下一步
调用MethodAccessor的invoke方法
  • Method.invoke() 不是自身实现反射调用逻辑,而是通过 sun.refelect.MethodAccessor 来处理
  • Method对象的基本构成:
    • 每个 Java 方法有且只有一个 Method 对象作为 root, 相当于根对象,对用户不可见
    • 当创建 Method 对象时,代码中获得的 Method 对象相当于其副本或者引用
    • root 对象持有一个 MethodAccessor 对象,所有获取到的 Method 对象都共享这一个 MethodAccessor 对象
    • 必须保证 MethodAccessor 在内存中的可见性
  • root对象及其声明:
private volatile MethodAccessor methodAccessor;
/**
 * For sharing of MethodAccessors. This branching structure is
 * currently only two levels deep (i.e., one root Method and
 * potentially many Method objects pointing to it.)
 * 
 * If this branching structure would ever contain cycles, deadlocks can
 * occur in annotation code.
 */
private Method  root;
  • MethodAccessor:
/**
 * This interface provides the declaration for
 * java.lang.reflect.Method.invoke(). Each Method object is
 * configured with a (possibly dynamically-generated) class which
 * implements this interface
 */
 public interface MethodAccessor {
 	// Matches specification in {@link java.lang.reflect.Method}
 	public Object invoke(Object obj, Object[] args) throws IllegalArgumentException, InvocationTargetException;
 }

MethodAccessor是一个接口,定义了 invoke() 方法,通过 Usage 可以看出 MethodAccessor 的具体实现类:

  1. sun.reflect.DelegatingMethodAccessorImpl
  2. sun.reflect.MethodAccessorImpl
  3. sun.reflect.NativeMethodAccessorImpl
  • 第一次调用 Java 方法对应的 Method 对象的 invoke() 方法之前,实现调用逻辑的 MethodAccess 对象还没有创建
  • 第一次调用时,才开始创建 MethodAccessor 并更新为 root, 然后调用 MethodAccessor.invoke() 完成反射调用
/**
 * NOTE that there is no synchronization used here. 
 * It is correct(though not efficient) to generate more than one MethodAccessor for a given Method.
 * However, avoiding synchronization will 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 = tmp;
	} else {
		tmp = reflectionFactory.newMethodAccessor(this);
		setMethodAccessor(tmp);
	} 
	return tmp;
}
  • methodAccessor 实例由 reflectionFactory 对象操控生成 ,reflectionFactory 是在 AccessibleObject 中声明的:
/**
 * Reflection factory used by subclasses for creating field,
 * method, and constructor accessors. Note that this is called very early in the bootstrapping process.
 */
static final ReflectionFactory reflectionFactory = AccessController.doPrivileged(
													new sun.reflect.ReflectionFactory.GetReflectionFactoryAction());
  • sun.reflect.ReflectionFactory 方法:
public class ReflectionFactory {
	private static boolean initted = false;
	private static Permission reflectionFactoryAccessPerm = new RuntimePermission("reflectionFactoryAccess");
	private static ReflectionFactory soleInstance = new ReflectionFactory();
	// Provides access to package-private mechanisms in java.lang.reflect
	private static volatile LangReflectAccess langReflectAccess;

	/**
	 * "Inflation" mechanism. Loading bytecodes to implement Method.invoke() and Constructor.
	 * newInstance() currently costs 3-4x more than an invocation via native code for the first invocation (though subsequent invocations have been benchmarked to be over 20x faster)
	 * Unfortunately this cost increases startup time for certain applications that use reflection intensively (but only once per class) to bootstrap themselves
	 * To avoid this penalty we reuse the existing JVM entry points for the first few invocations of Methods and Constructors and then switch to the bytecode-based implementations
	 */

	// Package-private to be accessible to NativeMethodAccessorImpl and NativeConstructorAccessorImpl
	private static noInflation = false;
	private static int inflationThreshold = 15;

	// 生成MethodAccessor
	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;
		}
	}

	/**
	 * We have to defer full initialization of this class until after the static initializer is run since java.lang.reflect
     * Method's static initializer (more properly, that for java.lang.reflect.AccessibleObject) causes this class's to be run, before the system properties are set up
	 */
	 private static void checkInitted() {
	 	if (initted) return;
	 	AccessController.doPrivileged(
            new PrivilegedAction<Void>() {
                public Void run() {
                /**
                 * Tests to ensure the system properties table is fully initialized
                 * This is needed because reflection code is called very early in the initialization process (before command-line arguments have been parsed and therefore these user-settable properties installed
                 * We assume that if System.out is non-null then the System class has been fully initialized and that the bulk of the startup code has been run
                 */
                 if (System.out == null) {
                        // java.lang.System not yet fully initialized
                        return null;
                    }
                    String val = System.getProperty("sun.reflect.noInflation");
                    if (val != null && val.equals("true")) {
                        noInflation = true;
                    }
                    val = System.getProperty("sun.reflect.inflationThreshold");
                    if (val != null) {
                        try {
                            inflationThreshold = Integer.parseInt(val);
                        } catch (NumberFormatException e) {
                            throw new RuntimeException("Unable to parse property sun.reflect.inflationThreshold", e);
                        }
                    }
                    initted = true;
                    return null;
                }
            });
    }
}
  • 实际的MethodAccessor实现有两个版本,一个是Java版本,一个是native版本,两者各有特点:
    • 初次启动时 Method.invoke()Constructor.newInstance() 方法采用native方法要比Java方法快3-4倍
    • 启动后 native 方法又要消耗额外的性能而慢于 Java 方法
    • Java实现的版本在初始化时需要较多时间,但长久来说性能较好
  • 这是HotSpot的优化方式带来的性能特性:
    • 跨越native边界会对优化有阻碍作用
  • 为了尽可能地减少性能损耗,HotSpot JDK采用inflation方式:
    • Java方法在被反射调用时,开头若干次使用native版
    • 等反射调用次数超过阈值时则生成一个专用的 MethodAccessor 实现类,生成其中的 invoke() 方法的字节码
    • 以后对该Java方法的反射调用就会使用Java版本
  • ReflectionFactory.newMethodAccessor()生成MethodAccessor对象的逻辑:
    • native 版开始会会生成 NativeMethodAccessorImplDelegatingMethodAccessorImpl 两个对象
  • DelegatingMethodAccessorImpl:
/* Delegates its invocation to another MethodAccessorImpl and can change its delegate at run time */
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;
    }
}

DelegatingMethodAccessorImpl对象是一个中间层,方便在 native 版与 Java 版的 MethodAccessor 之间进行切换

  • native版MethodAccessor的Java方面的声明: sun.reflect.NativeMethodAccessorImpl
/* Used only for the first few invocations of a Method; afterward,switches to bytecode-based implementation */
class NativeMethodAccessorImpl extends MethodAccessorImpl {
    private 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);
}
  • 每次 NativeMethodAccessorImpl.invoke() 方法被调用时,程序调用计数器都会增加 1, 看看是否超过阈值
  • 如果超过则调用 MethodAccessorGenerator.generateMethod() 来生成 Java 版的 MethodAccessor 的实现类
  • 改变 DelegatingMethodAccessorImpl 所引用的 MethodAccessorJava
  • 经由 DelegatingMethodAccessorImpl.invoke() 调用到的就是 Java 版的实现

JVM层invoke0方法

  • invoke0 方法是一个 native 方法,在 HotSpot JVM 里调用 JVM_InvokeMethod 函数:
JNIEXPORT jobject JNICALL Java_sun_reflect_NativeMethodAccessorImpl_invoke0
(JNIEnv *env, jclass unused, jobject m, jobject obj, jobjectArray args)
{
    return JVM_InvokeMethod(env, m, obj, args);
}
  • openjdk/hotspot/src/share/vm/prims/jvm.cpp:
JVM_ENTRY(jobject, JVM_InvokeMethod(JNIEnv *env, jobject method, jobject obj, jobjectArray args0))
  JVMWrapper("JVM_InvokeMethod");
  Handle method_handle;
  if (thread->stack_available((address) &method_handle) >= JVMInvokeMethodSlack) {
    method_handle = Handle(THREAD, JNIHandles::resolve(method));
    Handle receiver(THREAD, JNIHandles::resolve(obj));
    objArrayHandle args(THREAD, objArrayOop(JNIHandles::resolve(args0)));
    oop result = Reflection::invoke_method(method_handle(), receiver, args, CHECK_NULL);
    jobject res = JNIHandles::make_local(env, result);
    if (JvmtiExport::should_post_vm_object_alloc()) {
      oop ret_type = java_lang_reflect_Method::return_type(method_handle());
      assert(ret_type != NULL, "sanity check: ret_type oop must not be NULL!");
      if (java_lang_Class::is_primitive(ret_type)) {
        // Only for primitive type vm allocates memory for java object.
        // See box() method.
        JvmtiExport::post_vm_object_alloc(JavaThread::current(), result);
      }
    }
    return res;
  } else {
    THROW_0(vmSymbols::java_lang_StackOverflowError());
  }
JVM_END
  • 关键部分为 Reflection::invoke_method: openjdk/hotspot/src/share/vm/runtime/reflection.cpp
oop Reflection::invoke_method(oop method_mirror, Handle receiver, objArrayHandle args, TRAPS) {
  oop mirror             = java_lang_reflect_Method::clazz(method_mirror);
  int slot               = java_lang_reflect_Method::slot(method_mirror);
  bool override          = java_lang_reflect_Method::override(method_mirror) != 0;
  objArrayHandle ptypes(THREAD, objArrayOop(java_lang_reflect_Method::parameter_types(method_mirror)));
  oop return_type_mirror = java_lang_reflect_Method::return_type(method_mirror);
  BasicType rtype;
  if (java_lang_Class::is_primitive(return_type_mirror)) {
    rtype = basic_type_mirror_to_basic_type(return_type_mirror, CHECK_NULL);
  } else {
    rtype = T_OBJECT;
  }
  instanceKlassHandle klass(THREAD, java_lang_Class::as_Klass(mirror));
  Method* m = klass->method_with_idnum(slot);
  if (m == NULL) {
    THROW_MSG_0(vmSymbols::java_lang_InternalError(), "invoke");
  }
  methodHandle method(THREAD, m);
  return invoke(klass, method, receiver, override, ptypes, rtype, args, true, THREAD);
}

Java的对象模型 :klassoop

Java版的实现

  • JavaMethodAccessor 的生成使用 MethodAccessorGenerator 实现
Generator for sun.reflect.MethodAccessor and
    sun.reflect.ConstructorAccessor objects using bytecodes to
    implement reflection. A java.lang.reflect.Method or
    java.lang.reflect.Constructor object can delegate its invoke or
    newInstance method to an accessor using native code or to one
    generated by this class. (Methods and Constructors were merged
    together in this class to ensure maximum code sharing.)

运用了 asm 动态生成字节码技术 - sun.reflect.ClassFileAssembler

invoke总结

  • invoke方法的过程:
    img
  • MagicAccessorImpl:
    • 原本Java的安全机制使得不同类之间不是任意信息都可见,但JDK里面专门设了个 MagicAccessorImpl 标记类开了个后门来允许不同类之间信息可以互相访问,由JVM管理
/** <P> MagicAccessorImpl (named for parity with FieldAccessorImpl and
    others, not because it actually implements an interface) is a
    marker class in the hierarchy. All subclasses of this class are
    "magically" granted access by the VM to otherwise inaccessible
    fields and methods of other classes. It is used to hold the code
    for dynamically-generated FieldAccessorImpl and MethodAccessorImpl
    subclasses. (Use of the word "unsafe" was avoided in this class's
    name to avoid confusion with {@link sun.misc.Unsafe}.) </P>
    <P> The bug fix for 4486457 also necessitated disabling
    verification for this class and all subclasses, as opposed to just
    SerializationConstructorAccessorImpl and subclasses, to avoid
    having to indicate to the VM which of these dynamically-generated
    stub classes were known to be able to pass the verifier. </P>
    <P> Do not change the name of this class without also changing the
    VM's code. </P> */
class MagicAccessorImpl {
}
  • @CallerSensitive注解
Summary: Improve the security of the JDK’s method-handle implementation by replacing the existing
 hand-maintained list of caller-sensitive methods with a mechanism that accurately identifies
  such methods and allows their callers to be discovered reliably.
/**
 * A method annotated @CallerSensitive is sensitive to its calling class,
 * via {@link sun.reflect.Reflection#getCallerClass Reflection.getCallerClass},
 * or via some equivalent.
 *
 * @author John R. Rose
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({METHOD})
public @interface CallerSensitive {
}
  • 用@CallerSensitive注解修饰的方法从一开始就知道具体调用此方法的对象
    • 不用再经过一系列的检查就能确定具体调用此方法的对象
    • 实际上是调用 sun.reflect.Reflection.getCallerClass 方法
  • Reflection类位于调用栈中的0帧位置
    • sun.reflect.Reflection.getCallerClass() 方法返回调用栈中从0帧开始的第x帧中的类实例
    • 该方法提供的机制可用于确定调用者类,从而实现"感知调用者(Caller Sensitive)"的行为
    • 即允许应用程序根据调用类或调用栈中的其它类来改变其自身的行为

反射注意点

  • 反射会额外消耗系统资源,如果不需要动态地创建一个对象,就不要使用反射
  • 反射调用方法时可以忽略权限检查.可能会破坏封装性而导致安全问题
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

深入理解Java中的反射机制和使用原理!详细解析invoke方法的执行和使用 的相关文章

  • 同一服务器上的许多应用程序具有相同的 JMX Mbean 类

    我有超过 5 个 Spring Web 应用程序 它们都在使用另一个通用库 这个公共库有它自己的 MBean 由于强制的唯一 objectName 约束 我的应用程序无法部署在同一服务器上 我使用 MBean 的方式是这样的 Managed
  • “源兼容性”和“目标兼容性”有什么区别?

    之间有什么关系 区别sourceCompatibility and targetCompatibility 当它们设置为不同的值时会发生什么 根据工具链和兼容性 https docs gradle org current userguide
  • java程序有多少种结束方式?

    我知道使用 System exit 0 可以结束一个java程序 例如 如果我有一个JFrame窗口 它会关闭并结束程序 但我想知道还有多少其他方法 可以关闭它并结束程序 包括发生错误时 程序会被关闭 JFrame也会被关闭吗 添加到其他答
  • 使用 proguard 混淆文件名

    我正在使用 proguard 和 Android Studio 混淆我的 apk 当我反编译我的apk时 我可以看到很多文件 例如aaa java aab java ETC 但我项目中的所有文件都有原始名称 有没有办法混淆我的项目的文件名
  • 在不支持 CAS 操作的处理器上进行 CompareAndSet

    今天 我在一次采访中被问到下一个问题 如果您在具有不支持 CAS 操作的处理器的机器上调用 AtomicLong 的compareAndSet 方法 会发生什么情况 您能否帮我解决这个问题 并在可能的情况下提供一些全面描述的链接 From
  • 有人用过 ServiceLoader 和 Guice 一起使用吗?

    我一直想通过我们的应用程序 构建系统进行更大规模的尝试 但更高的优先级不断将其推到次要地位 这似乎是加载 Guice 模块的好方法 并且避免了关于 硬编码配置 的常见抱怨 单个配置属性很少会自行更改 但您几乎总是会有一组配置文件 通常用于不
  • 我对线程失去了理智

    我想要这个类的对象 public class Chromosome implements Runnable Comparable
  • 未装饰窗户的 Windows Snap 功能?

    有谁知道如何允许未装饰的窗户使用此功能 唯一的选择就是重新实施它 有任何想法吗 谢谢 可停靠可能是唯一的JToolBar http docs oracle com javase tutorial uiswing components too
  • 如何使用 Java 引用释放 Java Unsafe 内存?

    Java Unsafe 类允许您按如下方式为对象分配内存 但是使用此方法在完成后如何释放分配的内存 因为它不提供内存地址 Field f Unsafe class getDeclaredField theUnsafe Internal re
  • ThreeTen 向后移植与 JSR-310 的比较

    由于某些原因 我们现在无法使用 java 8 我们仍然停留在 java 7 上 不过 我想使用新的JSR 310 date time APIs现在 使用官方向后移植 ThreeTen http www threeten org threet
  • 在光标所在行强制关闭!

    嘿 我正在尝试创建一个应用程序来查找存储在 SQlite 数据库中的 GPS 数据 但我面临一个问题 我构建了一个 DbAdapter 类来创建数据库 现在我尝试使用以下函数从另一个类获取所有数据上的光标 public Cursor fet
  • 在 Spring 中为 @Pathvariable 添加类级别验证

    在发布这个问题之前 我已经做了很多研究并尝试了很多可用的解决方案 这是我陷入的棘手情况 我有一个 Spring 控制器 它有多个请求映射 它们都有 PathVariables 控制器如下所示 Controller EnableWebMvc
  • Android - 存储对ApplicationContext的引用

    我有一个静态 Preferences 类 其中包含一些应用程序首选项和类似的内容 可以在那里存储对 ApplicationContext 的引用吗 我需要该引用 以便我可以在不继承 Activity 的类中获取缓存文件夹和类似内容 你使用的
  • Joshua Bloch 的构建器设计模式有何改进?

    早在 2007 年 我就读过一篇关于 Joshua Blochs 所采用的 构建器模式 的文章 以及如何修改它以改善构造函数和 setter 的过度使用 特别是当对象具有大量属性 其中大部分属性是可选的 时 本文对此设计模式进行了简要总结
  • 如何使用 Jersey 将嵌套列表封送为 JSON?我得到一个空数组或一个包含数组的单元素字典数组

    我正在开发一个使用 Jersey 将对象转换为 JSON 的项目 我希望能够写出嵌套列表 如下所示 data one two three a b c 我想要转换的对象首先将数据表示为 gt gt 我认为 Jersey 会做正确的事情 以上输
  • java实现excel价格、收益率函数[关闭]

    就目前情况而言 这个问题不太适合我们的问答形式 我们希望答案得到事实 参考资料或专业知识的支持 但这个问题可能会引发辩论 争论 民意调查或扩展讨论 如果您觉得这个问题可以改进并可能重新开放 访问帮助中心 help reopen questi
  • Spring Data Rest 多对多 POST

    首先 让我解释一下我的用例 这非常简单 有一个用户实体和一个服务实体 我使用 UserService 作为连接实体 连接表 在用户和服务之间建立多对多关联最初 会有一些用户集和一些服务集 用户可以在任何时间点订阅任何服务 在这种情况下 将向
  • 在 Java 中通过 D-Bus MPRIS 访问 Clementine 实例

    我使用 Clementine 作为音乐播放器 它可以通过 D Bus 命令进行控制 在命令行上 使用 qdbus 我可以 Start Stop 暂停播放器 强制它跳过播放列表中的歌曲 检查播放列表的长度 检查播放列表中当前播放的曲目及其元数
  • 检测到 JVM 正在关闭

    我有一个使用 addShutdownHook 处理 Ctrl C 的 Swing 应用程序 它工作正常 直到我的关闭任务之一调用一个在正常情况下更改 JLabel 文本的函数 此时它挂起 我认为问题是 Swing EDT 已终止或正在等待某
  • Spring 作为 JNDI 提供者?

    我想使用 Spring 作为 JNDI 提供程序 这意味着我想在 Spring 上下文中配置一个 bean 可以通过 JNDI 访问该 bean 这看起来像这样

随机推荐

  • cmake使用总结

    官方文档CMake Reference Documentation CMake 3 7 2 Documentation CMake是一个跨平台的安装 编译 工具 可以用简单的语句来描述所有平台的安装 编译过程 输出各种各样的makefile
  • 老电脑装Win11的步骤

    去UUP dump选择最新的win11 pro 运行脚本生成ISO文件 使用 https github com AveYo MediaCreationTool bat tree main bypass11 此脚本对ISO文件进行处理 让其可
  • 李开复硅谷之行感悟:跟他们比,我们的创业者现在最缺什么?

    李开复硅谷之行感悟 跟他们比 我们的创业者现在最缺什么 创业10日谈 2016 03 04 i黑马 15天 100人 2016年新年伊始 李开复亲自带队奔赴硅谷 26位鼎鼎大佬 DST米尔纳 Google皮猜 雅虎杨致远 YC孵化器SAM
  • R语言中rattle安装,GTK+反复不成功的问题

    1 首先百度到R语言官网下载最新的R语言环境 2 安装Rstudio去官网下载最新的Rstudio版本安装 如果下载太慢 可以通过百度网盘来下载 链接 https pan baidu com s 1N9eDa14Z5D dUQ5jH LDH
  • 【Leetcode】二叉树刷题I:226/116/114

    还是喜欢手写笔记 这里就直接附上笔记图片和代码 Cpp 学习资源 公众号labuladong 一 二叉树总述 二 leetcode226 Definition for a binary tree node struct TreeNode i
  • shell脚本的正则表达式

    一 概念 正则表达式是通过一些特殊字符的排序 用以删除 查找 替换一行或者多行文字字符串的程序 二 特殊字符 1 字符类 注意 任意字符 与重复字符 1 小数点 代表一定有一个任意字符的意思 2 星号 代表重复前一个0到无穷多次的意思 为组
  • unity台桌小游戏

    1 创建桌面 新建一个empty命名为table 创建子物体plane和四个cube 调整位置和大小 并赋予材质设置颜色 table cube1 cube2 cube3 cube4 2 给主相机添加代码 使相机始终跟随小球 using Sy
  • 卡尔曼滤波推导笔记

    文章目录 卡尔曼滤波 Kalman Filter 第一节 递归算法 第二节 数学基础 数据融合 Data Fuslen 协方差矩阵 Covariance Matrix 状态空间表达 State Space Representation 第三
  • VOC数据集和COCO数据集直接的相互转换

    VOC数据集 xml格式 和COCO数据集 json格式 的相互转换 我们先来看看voc和coco数据集的目录结构 以VOC2012数据集为例 下载下来有如下五个文件夹 Annotations文件夹是存放图片对应的xml文件 比如 2007
  • Intellij Idea 将java项目打包成jar

    1 菜单 File gt project stucture 2 在弹窗最左侧选中Artifacts gt 选jar 选择from modules with dependencies 然后会有配置窗口出现 配置完成后 勾选Build on m
  • Sklearn机器学习中fit,transform, fit_transform的区别

    一 简介 机器学习是从大量的数据中学习到相关的规律和逻辑 然后利用他们来预测未知的事物 它通过学习模拟人类的学习行为 能够自身组织和整理已学习到的知识 并在应用中不断地完善自身缺陷 二 机器学习的步骤 获取数据 数据处理 特征工程 标准化
  • QT添加外部库使用方法

    1 编译库 明确一点 不同编译器编译出来的库不一定可以互相使用的 所以尽量你的库文件是使用同一个编译器编译出来 首先找到你的qt所使用的编译器是哪个 一般会在QT的安装目录下的tools文件夹下 比如 D QT Tools mingw492
  • 基于RFID技术应用于图书档案管理

    基于RFID技术应用于图书档案管理 RFID是一种非接触式自动识别技术 通过射频信号自动识别目标对象并获取相关数据 它具有远距离 批量读取 可识别静止和运动状态下的物品信息 同传统的以条形码为代表的自动识别技术相比 RFID技术具有数据自动
  • 【笔记】行人属性识别

    论文 https arxiv org pdf 1901 07474 pdf 以下序号与论文序列不对应 属性可以看作是高层语义信息 对视点变化和观察条件多样性具有更强的鲁棒性 本文试图解决以下几个重要问题 1 传统和基于深度学习的行人属性识别
  • 华为OD机试 Python 【不开心的小朋友】

    描述 在游乐园里 有一些受小孩欢迎的摇摇车 但每辆车只能由一个小孩使用 其他的小孩要么选择等待要么选择离开 那些选择等待但最后未能玩的小孩会感到不开心 你能帮忙计算出哪些小孩会不开心吗 输入 摇摇车的数量 介于1到10之间 小孩的行动序列
  • yolov5源码--网络结构模块

    网络结构模块 可视化 网络配置文件 网络结构解读 代码解读 前向传播 特征提取网络 特征融合网络 检测头 可视化 python models export py weights weights yolov5s pt img 640 batc
  • 重磅:某国产IDE发布,称完全可替代 IntelliJ IDEA

    8 月下旬 一个号称 自主研发 的集成开发环境工具 CEC IDE 被多方质疑造假 最终在 8 月 26 日以官方出面致歉作结 这一事件虽然已经告一段落 但最近关于国产IDE的讨论也有所上升 日前 又一款宣称 纯自研 的国产IDE亮相了 桌
  • (模拟栈 单调栈)acwing828. 模拟栈 830. 单调栈算法基础班第二讲

    题目一 acwing828 模拟栈 实现一个栈 栈初始为空 支持四种操作 push x 向栈顶插入一个数 x pop 从栈顶弹出一个数 empty 判断栈是否为空 query 查询栈顶元素 现在要对栈进行 M 个操作 其中的每个操作 3 和
  • ETL加载策略

    2019独角兽企业重金招聘Python工程师标准 gt gt gt ETL 数据加载机制概述 ETL 是数据抽取 Extract 转换 Transform 加载 Load 的简写 它的功能是从数据源抽取出所需的数据 经过数据清洗和转换 最终
  • 深入理解Java中的反射机制和使用原理!详细解析invoke方法的执行和使用

    反射的概念 反射 Refelection 反射是Java的特征之一 允许运行中的Java程序获取自身信息 并可以操作类或者对象的内部属性 通过反射 可以在运行时获得程序或者程序中的每一个类型的成员活成成员的信息 程序中的对象一般都是在编译时