关于反射调用方法的一个log
刚才在JavaEye问答频道看到了这么一个问题:
请问报这个是什么意思?
这是Sun实现的Java标准库的一个细节。下面举例稍微讲解一下。
假如有这么一个类A:
- public class A {
- public void foo(String name) {
- System.out.println("Hello, " + name);
- }
- }
可以编写另外一个类来反射调用A上的方法:
- import java.lang.reflect.Method;
- public class TestClassLoad {
- public static void main(String[] args) throws Exception {
- Class<?> clz = Class.forName("A");
- Object o = clz.newInstance();
- Method m = clz.getMethod("foo", String.class);
- for (int i = 0; i < 16; i++) {
- m.invoke(o, Integer.toString(i));
- }
- }
- }
注意到TestClassLoad类上不会有对类A的符号依赖——也就是说在加载并初始化TestClassLoad类时不需要关心类A的存在与否,而是等到main()方法执行到调用Class.forName()时才试图对类A做动态加载;这里用的是一个参数版的forName(),也就是使用当前方法所在类的ClassLoader来加载,并且初始化新加载的类。……好吧这个细节跟主题没啥关系。
回到主题。这次我的测试环境是Sun的JDK 1.6.0 update 13 build 03。编译上述代码,并在执行TestClassLoad时加入-XX:+TraceClassLoading参数(或者-verbose:class或者直接-verbose都行),如下:
- java -XX:+TraceClassLoading TestClassLoad
可以看到输出了一大堆log,把其中相关的部分截取出来如下:(完整的log可以从附件下载)
- [Loaded TestClassLoad from file:/D:/temp_code/test_java_classload/]
- [Loaded A from file:/D:/temp_code/test_java_classload/]
- [Loaded sun.reflect.NativeMethodAccessorImpl from shared objects file]
- [Loaded sun.reflect.DelegatingMethodAccessorImpl from shared objects file]
- Hello, 0
- Hello, 1
- Hello, 2
- Hello, 3
- Hello, 4
- Hello, 5
- Hello, 6
- Hello, 7
- Hello, 8
- Hello, 9
- Hello, 10
- Hello, 11
- Hello, 12
- Hello, 13
- Hello, 14
- [Loaded sun.reflect.ClassFileConstants from shared objects file]
- [Loaded sun.reflect.AccessorGenerator from shared objects file]
- [Loaded sun.reflect.MethodAccessorGenerator from shared objects file]
- [Loaded sun.reflect.ByteVectorFactory from shared objects file]
- [Loaded sun.reflect.ByteVector from shared objects file]
- [Loaded sun.reflect.ByteVectorImpl from shared objects file]
- [Loaded sun.reflect.ClassFileAssembler from shared objects file]
- [Loaded sun.reflect.UTF8 from shared objects file]
- [Loaded java.lang.Void from shared objects file]
- [Loaded sun.reflect.Label from shared objects file]
- [Loaded sun.reflect.Label$PatchInfo from shared objects file]
- [Loaded java.util.AbstractList$Itr from shared objects file]
- [Loaded sun.reflect.MethodAccessorGenerator$1 from shared objects file]
- [Loaded sun.reflect.ClassDefiner from shared objects file]
- [Loaded sun.reflect.ClassDefiner$1 from shared objects file]
- [Loaded sun.reflect.GeneratedMethodAccessor1 from __JVM_DefineClass__]
- Hello, 15
可以看到前15次反射调用A.foo()方法并没有什么稀奇的地方,但在第16次反射调用时似乎有什么东西被触发了,导致JVM新加载了一堆类,其中就包括[Loaded sun.reflect.GeneratedMethodAccessor1 from __JVM_DefineClass__]这么一行。这是哪里来的呢?
先来看看JDK里Method.invoke()是怎么实现的。
java.lang.reflect.Method:
- public final
- class Method extends AccessibleObject implements GenericDeclaration,
- Member {
- // ...
- 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.)
- private Method root;
- // ...
- public Object invoke(Object obj, Object... args)
- throws IllegalAccessException, IllegalArgumentException,
- InvocationTargetException
- {
- if (!override) {
- if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
- Class caller = Reflection.getCallerClass(1);
- Class targetClass = ((obj == null || !Modifier.isProtected(modifiers))
- ? clazz
- : obj.getClass());
- boolean cached;
- synchronized (this) {
- cached = (securityCheckCache == caller)
- && (securityCheckTargetClassCache == targetClass);
- }
- if (!cached) {
- Reflection.ensureMemberAccess(caller, clazz, obj, modifiers);
- synchronized (this) {
- securityCheckCache = caller;
- securityCheckTargetClassCache = targetClass;
- }
- }
- }
- }
- if (methodAccessor == null) acquireMethodAccessor();
- return methodAccessor.invoke(obj, args);
- }
- // 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 void 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;
- return;
- }
- // Otherwise fabricate one and propagate it up to the root
- tmp = reflectionFactory.newMethodAccessor(this);
- setMethodAccessor(tmp);
- }
- // ...
- }
可以看到Method.invoke()实际上并不是自己实现的反射调用逻辑,而是委托给sun.reflect.MethodAccessor来处理。
每个实际的Java方法只有一个对应的Method对象作为root,。这个root是不会暴露给用户的,而是每次在通过反射获取Method对象时新创建Method对象把root包装起来再给用户。在第一次调用一个实际Java方法对应得Method对象的invoke()方法之前,实现调用逻辑的MethodAccessor对象还没创建;等第一次调用时才新创建MethodAccessor并更新给root,然后调用MethodAccessor.invoke()真正完成反射调用。
那么MethodAccessor是啥呢?
sun.reflect.MethodAccessor:
- public interface MethodAccessor {
- /** Matches specification in {@link java.lang.reflect.Method} */
- public Object invoke(Object obj, Object[] args)
- throws IllegalArgumentException, InvocationTargetException;
- }
可以看到它只是一个单方法接口,其invoke()方法与Method.invoke()的对应。
创建MethodAccessor实例的是ReflectionFactory。
sun.reflect.ReflectionFactory:
- public class ReflectionFactory {
- private static boolean initted = false;
- // ...
- //
- // "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 boolean noInflation = false;
- private static int inflationThreshold = 15;
- // ...
- /** 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() {
- public Object 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 (RuntimeException)
- new RuntimeException("Unable to parse property sun.reflect.inflationThreshold").
- initCause(e);
- }
- }
- initted = true;
- return null;
- }
- });
- }
- // ...
- public MethodAccessor newMethodAccessor(Method method) {
- checkInitted();
- if (noInflation) {
- 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;
- }
- }
- }
这里就可以看到有趣的地方了。如注释所述,实际的MethodAccessor实现有两个版本,一个是Java实现的,另一个是native code实现的。Java实现的版本在初始化时需要较多时间,但长久来说性能较好;native版本正好相反,启动时相对较快,但运行时间长了之后速度就比不过Java版了。这是HotSpot的优化方式带来的性能特性,同时也是许多虚拟机的共同点:跨越native边界会对优化有阻碍作用,它就像个黑箱一样让虚拟机难以分析也将其内联,于是运行时间长了之后反而是托管版本的代码更快些。
为了权衡两个版本的性能,Sun的JDK使用了“inflation”的技巧:让Java方法在被反射调用时,开头若干次使用native版,等反射调用次数超过阈值时则生成一个专用的MethodAccessor实现类,生成其中的invoke()方法的字节码,以后对该Java方法的反射调用就会使用Java版。
Sun的JDK是从1.4系开始采用这种优化的,主要作者是Ken Russell
上面看到了ReflectionFactory.newMethodAccessor()生产MethodAccessor的逻辑,在“开头若干次”时用到的DelegatingMethodAccessorImpl代码如下:
sun.reflect.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;
- }
- }
这是一个间接层,方便在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
- {
- if (++numInvocations > ReflectionFactory.inflationThreshold()) {
- 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()方法被调用时,都会增加一个调用次数计数器,看超过阈值没有;一旦超过,则调用MethodAccessorGenerator.generateMethod()来生成Java版的MethodAccessor的实现类,并且改变DelegatingMethodAccessorImpl所引用的MethodAccessor为Java版。后续经由DelegatingMethodAccessorImpl.invoke()调用到的就是Java版的实现了。
注意到关键的invoke0()方法是个native方法。它在HotSpot VM里是由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);
- }
- 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():
- // This would be nicer if, say, java.lang.reflect.Method was a subclass
- // of java.lang.reflect.Constructor
- 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_klassOop(mirror));
- methodOop 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);
- }
再下去就深入到HotSpot VM的内部了,本文就在这里打住吧。有同学有兴趣深究的话以后可以再写一篇讨论native版的实现。
回到Java的一侧。MethodAccessorGenerator长啥样呢?由于代码太长,这里就不完整贴了,有兴趣的可以到OpenJDK 6的Mercurial仓库看:OpenJDK 6 build 17的MethodAccessorGenerator。它的基本工作就是在内存里生成新的专用Java类,并将其加载。就贴这么一个方法:
- private static synchronized String generateName(boolean isConstructor,
- boolean forSerialization)
- {
- if (isConstructor) {
- if (forSerialization) {
- int num = ++serializationConstructorSymnum;
- return "sun/reflect/GeneratedSerializationConstructorAccessor" + num;
- } else {
- int num = ++constructorSymnum;
- return "sun/reflect/GeneratedConstructorAccessor" + num;
- }
- } else {
- int num = ++methodSymnum;
- return "sun/reflect/GeneratedMethodAccessor" + num;
- }
- }
去阅读源码的话,可以看到MethodAccessorGenerator是如何一点点把Java版的MethodAccessor实现类生产出来的。也可以看到GeneratedMethodAccessor+数字这种名字是从哪里来的了,就在上面的generateName()方法里。
对本文开头的例子的A.foo(),生成的Java版MethodAccessor大致如下:
- package sun.reflect;
- public class GeneratedMethodAccessor1 extends MethodAccessorImpl {
- public GeneratedMethodAccessor1() {
- super();
- }
- public Object invoke(Object obj, Object[] args)
- throws IllegalArgumentException, InvocationTargetException {
- // prepare the target and parameters
- if (obj == null) throw new NullPointerException();
- try {
- A target = (A) obj;
- if (args.length != 1) throw new IllegalArgumentException();
- String arg0 = (String) args[0];
- } catch (ClassCastException e) {
- throw new IllegalArgumentException(e.toString());
- } catch (NullPointerException e) {
- throw new IllegalArgumentException(e.toString());
- }
- // make the invocation
- try {
- target.foo(arg0);
- } catch (Throwable t) {
- throw new InvocationTargetException(t);
- }
- }
- }
就反射调用而言,这个invoke()方法非常干净(然而就“正常调用”而言这额外开销还是明显的)。注意到参数数组被拆开了,把每个参数都恢复到原本没有被Object[]包装前的样子,然后对目标方法做正常的invokevirtual调用。由于在生成代码时已经循环遍历过参数类型的数组,生成出来的代码里就不再包含循环了。
当该反射调用成为热点时,它甚至可以被内联到靠近Method.invoke()的一侧,大大降低了反射调用的开销。而native版的反射调用则无法被有效内联,因而调用开销无法随程序的运行而降低。
虽说Sun的JDK这种实现方式使得反射调用方法成本比以前降低了很多,但Method.invoke()本身要用数组包装参数;而且每次调用都必须检查方法的可见性(在Method.invoke()里),也必须检查每个实际参数与形式参数的类型匹配性(在NativeMethodAccessorImpl.invoke0()里或者生成的Java版MethodAccessor.invoke()里);而且Method.invoke()就像是个独木桥一样,各处的反射调用都要挤过去,在调用点上收集到的类型信息就会很乱,影响内联程序的判断,使得Method.invoke()自身难以被内联到调用方。
相比之下JDK 7里新的MethodHandle则更有潜力,在其功能完全实现后能达到比普通反射调用方法更高的性能。在使用MethodHandle来做反射调用时,MethodHandle.invoke()的形式参数与返回值类型都是准确的,所以只需要在链接方法的时候才需要检查类型的匹配性,而不必在每次调用时都检查。而且MethodHandle是不可变值,在创建后其内部状态就不会再改变了;JVM可以利用这个知识而放心的对它做激进优化,例如将实际的调用目标内联到做反射调用的一侧。
到本来Java的安全机制使得不同类之间不是任意信息都可见,但Sun的JDK里开了个口,有一个标记类专门用于开后门:
- package sun.reflect;
- /** <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 {
- }
那个"__JVM_DefineClass__"的来源是这里:
src/share/vm/prims/jvm.cpp
- // common code for JVM_DefineClass() and JVM_DefineClassWithSource()
- // and JVM_DefineClassWithSourceCond()
- static jclass jvm_define_class_common(JNIEnv *env, const char *name,
- jobject loader, const jbyte *buf,
- jsize len, jobject pd, const char *source,
- jboolean verify, TRAPS) {
- if (source == NULL) source = "__JVM_DefineClass__";
OK,本文就记到这里吧。希望对问答频道提问那位同学有帮助。
Have fun ^_^
============================================================================
本文转自其他作者,版权归原作者。
原帖地址:http://rednaxelafx.iteye.com/blog/548536