Deeply understand the reflection mechanism and use principle in Java! Analyze the execution and use of invoke method in detail

The concept of reflection

  • Reflection: reflection is one of the characteristics of Java. It allows running Java programs to obtain their own information and manipulate the internal properties of classes or objects
    • Through reflection, you can get the information of the program or each type of member in the program living into a member at run time
    • The objects in the program are generally determined at compile time. Java reflection mechanism can dynamically create objects and call relevant properties. The type of these objects is unknown at compile time
    • That is, objects can be created directly through the reflection mechanism, even if the object type is unknown at compile time
  • Java reflection provides the following functions:
    • Determine the class of any object at run time
    • Construct an object of any class at run time
    • Judge the member variables and methods of any class at runtime, and call the private method through reflection
    • Call the method of any object at run time

Principle of reflection

  • The core of reflection: the JVM dynamically loads classes or invokes methods and accesses properties at runtime, and does not need to know what the running object is in advance (such as at compile time)
  • Class loading:
    • Java reflection mechanism is developed around Class
    • First, understand the loading mechanism of classes:
      • The JVM uses ClassLoader to load bytecode files, that is, class files, into the method area memory
Class clazz = ClassLoader.getSystemClassLoader().loadClass("com.mypackage.MyClass");

The ClassLoader Class loads the Class according to the fully qualified name of the Class and returns a Class object

  • ReflectionData:
    • In order to improve the performance of reflection, cache must be provided
    • Class class uses a useCaches static variable to mark whether to use cache
    • This value can be through the external sun reflect. Nocache configures whether caching is disabled
    • Class class provides a reflectiondata internal class to store the cache of reflection data, and declares a reflectiondata field
    • This field does not point to an instantiated ReflectionData object due to a later on-demand deferred load and cache
// Whether to use cache can be marked through external sun reflect. Nocache configures whether caching is disabled
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;
	}
}
	
	// This is a SoftReference, which may be recycled when memory resources are tight
	// volatile ensures the correctness of reading and writing in multithreaded environment
	 private volatile transient SoftReference<RefelectionData<T>> reflectionData;

	// This is mainly used to compare with redefinitedcount in ReflectionData
	// If the two values are not equal, the data cached by ReflectionData has expired
	private volatile transient classRedefinedCount = 0;

Main uses of reflection

  • The most important use of reflection is to develop various common frameworks
    • Many frameworks are configured, and beans are configured through XML files
    • In order to ensure the universality of the framework, you need to load different objects or classes and call different methods according to the configuration file
    • To use reflection, the runtime dynamically loads the objects that need to be loaded
  • Example:
    • In the development of Struts 2 framework, it will be in struts Configure Action in XML:
<action name="login"
               class="org.ScZyhSoft.test.action.SimpleLoginAction"
               method="execute">
           <result>/shop/shop-index.jsp</result>
           <result name="error">login.jsp</result>
       </action>
  • Mapping relationship between configuration file and Action
  • When the View layer makes a request, the request will be intercepted by struts prepareandexecutefilter
  • Struts prepareandexecutefilter will dynamically create an Action instance
    • Request login action
    • Struts prepareandexecutefilter parses struts XML file
    • Retrieve the Action with name login in the Action
    • Create a SimpleLoginAction instance based on the class attribute
    • Use the invoke method to call the execute method
  • Reflection is the core of various container implementations

Application of reflection

  • Reflection related classes are in the struts prepareandexecutefilter package
  • Reflections can be used to:
    • Judge the class to which the object belongs
    • Get class object
    • Construct any object
    • Call an object
  • Nine predefined Class objects:
    • Basic Java types: boolean, byte, char, short, int, long, float, double
    • The keyword void is also represented as a class object through the of the class attribute
    • Static TYPE field of wrapper class or void class:
      • Integer.TYPE == int.class
      • Integer.class == int.class
    • Class instance object of array type:
      • Class<String[]> clz = String[].class;
      • How to compare whether the Class objects of the array are equal:
        • Dimension of array
        • Type of array
        • isArray() in Class is used to judge whether it represents an array type

Get Class object

  • Use the forName static method of Class:
public static Class<?> forName(String className);



/* Use this method to load the database driver in JDBC */
Class.forName(driver);
  • Get the class of an object directly:
Class<?> klass=int.class;
Class<?> classInt=Integer.TYPE;
  • Call the getClass() method of the object:
StringBuilder str=new StringBuilder("A");
Class<?> klass=str.getClass();

Determine whether it is an instance of a class

  • Generally speaking, use the instanceof keyword to determine whether it is an instance of a class
  • In reflection, you can use the isInstance() method of the Class object to determine whether it is an instance of a Class. This is a native method
public native boolean isInstance(Object obj);

Create instance

There are two main ways to generate instances of objects through reflection:

  • Use the newInstance() method of the Class object to create an instance of the Class corresponding to the Class object:
Class<?> c = String.class;
Object str = c.newInstance();
  • First obtain the specified Constructor object through the Class object, and then call the newInstance() method of the Constructor object to create an instance: you can use the specified Constructor to construct an instance of the Class
/* Get the Class object corresponding to String */
Class<?> c=String.class;

/* Gets the constructor of a String class with a String parameter */
Constructor constructor=c.getConstructor(String.class);

/* Create instance from constructor */
Object obj=constructor.newInstance("abc");
System.out.println(obj);

Acquisition method

There are three main methods to get the method set of Class object:

  • getDeclaredMethods():

    Returns all methods declared by a class or interface:

    • Including public, protected, default (package) access and private methods
    • Excluding inherited methods
public Method[] getDeclaredMethods() throws SecurityException {}
  • getMethods(): returns all public methods of a class
    • public methods including inherited classes
public Method[] getMethods() throws SecurityException {}
  • getMethod():

    Returns a specific method

    • First parameter: method name
    • The following parameters: the parameters of the method correspond to the Class object
public Method getMethod(String name,Class<?>... parameterType) {}
  • Example of acquisition method:
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();
		// Gets the add method in the methodClass class
		Method method = c.getMethod("add", int.class, int.class);
		// All methods obtained by getMethods() method
		System.out.println("getMethods Method of obtaining:");
		for (Method m:methods)
			System.out.println(m);
		// All methods obtained by getDeclaredMethods() method
		System.out.println("getDeclaredMethods Method of obtaining:");
		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;
	}
}

Program running results:

getMethods Method of obtaining:
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 Method of obtaining:
public int org.ScZyhSoft.common.methodClass.add(int,int)
public int org.ScZyhSoft.common.methodClass.sub(int,int)

The method obtained through getMethods() can obtain the method of the parent class

Get constructor information

  • Get an instance of the Constructor Class through the getConstructor method of the Class class
  • The newInstance method in the Constructor class can create an instance of an object:
public T newInstance(Objec ... initargs)

The newInstance method can call the corresponding Constructor to create an instance of the object according to the passed in parameters

Get the member variable information of the class

  • Getfiles: get public member variables
  • Getdeclaraedfields: get all declared member variables, but cannot get the member variables of the parent class

Call method

  • After getting a method from the class, you can use invoke() to call the method
public Object invoke(Object obj, Object ... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {}
  • Example:
public class InvokeTest {
	public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
		Class<?> klass = method.class;
		// Create an instance of methodClass
		Object obj = klass.newInstance();
		// Get the add method of methodClass
		Method method = klass.getMethod("add", int.class, int.class);
		// Call the method corresponding to method to implement 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;
	}
}

Creating arrays with reflection

  • Array is a special data type in Java, which can be assigned to an Object Reference
  • Example of creating an array with reflection:
public static void ArrayTest() throws ClassNotFoundException {
	Class<?> cls = class.forName("java.lang.String");
	Object array = Array.newInstance(cls, 25);
	// Adding data to an array
	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");
	// Get an item in the data
	System.out.println(Array.get(array, 3));
}

The array class is Java lang.reflect. Array class, through array Newinstance() creates an array object:

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

The newArray method is a native method, which is specifically implemented in the HotSpot JVM. The source code is as follows:

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

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

- newArray Source directory: 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);
  }
}
  • The set and get methods of the Array class are both native methods, which are specifically implemented in the HotSpot JVM. The corresponding relationship is as follows:
    • set: Reflection::array_set
    • get: Reflection::array_get

invoke method

  • In Java, many methods will call the invoke method, and many exceptions will be thrown to the invoke method:
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 execution process

  • The invoke method is used to dynamically call the method of an instance at runtime. The implementation is as follows:
@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);
}

Permission check

  • AccessibleObject class is the base class of Field,Method and Constructor objects:
    • Provides the ability to mark reflected objects to cancel the default Java language access control check when used
  • The invoke method will first check the value of the override attribute of the AccessibleObject:
    • The default value of override is false:
      • Indicates that permission is required to call rules, and permission needs to be checked when calling methods
      • You can also set setAccessible() to true
    • override if the value is true:
      • Indicates that the permission rule is ignored and there is no need to check the permission when calling the method
      • That is, you can call any private method, which violates the encapsulation
  • If the override attribute is false by default, further permission checks will be performed:
  1. First, use reflection The quickcheckmemberaccess (clazz, modifiers) method checks whether the method is public

    1.1 if it is a public method, skip this step

    1.2 if it is not a public method, use reflection Getcallerclass () method gets the Class object that calls this method, which is a native method

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

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


- stay OpenJDK Can be found in getCallerClass Methodical JNI entrance-Reflection.c

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

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

- JVM_GetCallerClass The source code is located in jvm.cpp in

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. After obtaining the Class object caller, use the checkAccess method to perform a quick permission verification. The implementation of the checkAccess method is as follows:
volatile Object securityCheckCache;

	void checkAccess(Class<?> caller, Class<?> clazz, Object obj, int modifiers) throws IllegalAccessException {
		if(caller == clazz){	// Fast check
			return;				// Permission passed verification
		}
		Object cache = securityCheckCache;	// Read volatile
		Class<?> targetClass = clazz;
		if (obj != null && Modifier.isProtected(modifiers) && ((targetClass = obj.getClass()) != clazz)) {	// Must match one of caller and targetclass
			if (cache instanceof Class[]) {
				Class<?>[] cache2 = (Class<?>[]) cache;
				if (cache2[1] == targetClass && cache[0] == caller) {
					return;		// Verification passed
				}
			}
		} else if (cache == caller) {
			return;				// Verification passed
		}
		slowCheckMemberAccess(caller, clazz, obj, modifiers, targetClass);
	}

First, perform a quick check. Once the Class is correct, the permission check passes; If it fails, a cache is created and checked in the middle

  • If all the above permission checks fail, a more detailed check will be performed:
void slowCheckMemberAccess(Class<?> caller, Class<?> clazz, Object obj, int modifiers, Class<?> targetClass) throws IllegalAccessException {
	Refelection.ensureMemberAccess(caller, clazz, obj, modifiers);
	// If successful, update the cache
	Object cache = ((targetClass == clazz) ? caller : new Class<?>[] {caller, targetClass});
	securityCheckCache = cache;
}

Use reflection The ensurememberaccess method continues to check permissions If the check passes, the cache will be updated, so that the next time the same class calls the same method, there will be no need to perform permission check. This is a simple caching mechanism

Because JMM's happens before rule can ensure that cache initialization can occur between write caches, the two caches do not need to be declared volatile

  • This concludes the work of checking permissions If it fails to pass the check, an exception will be thrown, and if it passes the check, it will go to the next step

Call the invoke method of MethodAccessor

  • Method.invoke() does not implement the reflection call logic by itself, but through sun refelect. Methodaccessor
  • Basic composition of Method object:
    • Each Java Method has and has only one Method object as root, which is equivalent to the root object and is invisible to the user
    • When creating a Method object, the Method object obtained in the code is equivalent to its copy or reference
    • The root object holds a MethodAccessor object, and all the obtained Method objects share this MethodAccessor object
    • The visibility of MethodAccessor in memory must be guaranteed
  • root object and its declaration:
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 is an interface that defines the invoke() method. The specific implementation class of MethodAccessor can be seen from Usage:

  1. sun.reflect.DelegatingMethodAccessorImpl
  2. sun.reflect.MethodAccessorImpl
  3. sun.reflect.NativeMethodAccessorImpl
  • The MethodAccess object that implements the calling logic has not been created before calling the invoke() Method of the Method object corresponding to the Java Method for the first time
  • When the first call is made, the MethodAccessor is created and updated to root, and then called MethodAccessor.. Invoke() completes the reflection call
/**
 * 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;
}
  • The methodAccessor instance is generated by the reflectionFactory object, which is declared in the 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 method:
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;

	// Generate 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;
                }
            });
    }
}
  • The actual implementation of MethodAccessor has two versions, one is Java version and the other is native version. They have their own characteristics:
    • Method at initial startup Invoke() and constructor The newinstance () method adopts the native method, which is 3-4 times faster than the Java method
    • After startup, the native method consumes additional performance and is slower than the Java method
    • The version implemented in Java takes more time to initialize, but the performance is good in the long run
  • This is the performance characteristic brought by HotSpot's optimization method:
    • Crossing the native boundary will hinder the optimization
  • In order to reduce performance loss as much as possible, HotSpot JDK adopts the inflation method:
    • When a Java method is called by reflection, the native version is used for the first few times
    • When the number of reflection calls exceeds the threshold, a special MethodAccessor implementation class is generated to generate the bytecode of the invoke() method
    • Later reflection calls to the Java method will use the Java version
  • ReflectionFactory.newMethodAccessor() generates the logic of MethodAccessor object:
    • At the beginning of the native version, two objects, NativeMethodAccessorImpl and DelegatingMethodAccessorImpl, will be generated
  • 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;
    }
}

The DelegatingMethodAccessorImpl object is an intermediate layer that facilitates switching between the native version and the Java version of MethodAccessor

  • Statement on Java aspect of native version of MethodAccessor: 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);
}
  • Every time nativemethodaccessorimpl When the invoke () method is called, the program call counter will increase by 1 to see if the threshold is exceeded
  • If it exceeds, call methodaccessorgenerator Generatemethod() to generate the implementation class of the Java version of MethodAccessor
  • Change the MethodAccessor referenced by DelegatingMethodAccessorImpl to Java version
  • Via delegatingmethodaccessorimpl Invoke () calls the Java implementation

JVM layer invoke0 method

  • The invoke0 method is a native method that calls the JVM in the HotSpot JVM_InvokeMethod function:
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
  • The key part is 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);
}

Object model of Java: klass and oop

Implementation of Java version

  • The generation of the Java version of the MethodAccessor is implemented using the 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.)

Using asm dynamic bytecode generation technology - sun reflect. ClassFileAssembler

invoke summary

  • Process of invoke method:
  • MagicAccessorImpl:
    • Originally, Java's security mechanism makes not any information between different classes visible, but a MagicAccessorImpl tag class is specially set in the JDK, which opens a back door to allow information between different classes to access each other and be managed by the 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 annotation
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 {
}
  • The method decorated with @ CallerSensitive annotation knows the specific object calling this method from the beginning
    • The specific object that calls this method can be determined without a series of checks
    • It's actually a call to sun reflect. Reflection. Getcallerclass method
  • The Reflection class is located at frame 0 in the call stack
    • sun. reflect. Reflection. The getcallerclass () method returns the class instance in frame x from frame 0 in the call stack
    • The mechanism provided by this method can be used to determine the caller class, so as to realize the behavior of "caller sensitive"
    • That is, the application is allowed to change its own behavior according to the calling class or other classes in the calling stack

Reflective attention point

  • Reflection consumes additional system resources. If you don't need to create an object dynamically, don't use reflection
  • Permission checking can be ignored when reflection calls a method It may destroy the encapsulation and cause safety problems

Keywords: Java jvm Programmer reflection

Added by alkhatareykha on Fri, 28 Jan 2022 17:00:20 +0200