Java 反射

什么是反射

反射是Java编程语言的一个特性。它允许正在执行的Java程序对自身进行检查、操纵程序的内部属性。例如,Java类可以获取其所有成员方法的名称并输出。

反射本质上也是一种元编程,“Program as data”。

示例代码

import java.lang.reflect.*;
 
   public class DumpMethods {
      public static void main(String args[])
      {
         try {
            Class c = Class.forName(args[0]);
            Method m[] = c.getDeclaredMethods();
            for (int i = 0; i < m.length; i++)
            System.out.println(m[i].toString());
         }
         catch (Throwable e) {
            System.err.println(e);
         }
      }
   }

实际使用的时候,我们一般都会调用方法。这种单纯看字段的场景很少,再来个例子:

public class A {
    public void foo(String name) {
        System.out.println("Hello, " + name);
    }
}
// 另一个文件
public class TestClassLoad {
    public static void main(String[] args) throws Exception {
        Class<?> clz = Class.forName("A");
        Object o = clz.newInstance();
        Method foo = clz.getMethod("foo", String.class);
        for (int i=0; i<16; i++) {
            foo.invoke(o, Integer.toString(i));
        }
    }
}

反射的性能

做个简单的测试吧。

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 foo = clz.getMethod("foo", String.class);
        int callTimes = 1000000;
        A a = new A();
        long startTime = System.nanoTime();
        for (int i=0; i<callTimes; i++) {
            foo.invoke(o, Integer.toString(i));
        }
        long endTime = System.nanoTime();
        String str1 = "reflect: " + (endTime - startTime) / callTimes + "per call";
        startTime = System.nanoTime();
        for (int i=0; i<callTimes; i++) {
            a.foo(Integer.toString(i));
        }
        endTime = System.nanoTime();
        String str2 = "direct: " + (endTime - startTime) / callTimes + "per call";
        System.out.println(str1);
        System.out.println(str2);
    }
}

最后的结果是:

reflect: 1524 per call
direct: 1199 per call

反射调用消耗的时间大概是直接调用的1.27倍。其实并没有很慢。

性能分析

那么慢的这一点到底慢在哪呢?以OpenJDK 17 为例,我们可以看一下invoke方法:

public Object invoke(Object obj, Object... args)
        throws IllegalAccessException, IllegalArgumentException,
           InvocationTargetException
    {
        if (!override) {
            Class<?> caller = Reflection.getCallerClass();
            checkAccess(caller, clazz,
                        Modifier.isStatic(modifiers) ? null : obj.getClass(),
                        modifiers);
        }
        MethodAccessor ma = methodAccessor;             // read volatile
        if (ma == null) {
            ma = acquireMethodAccessor();
        }
        return ma.invoke(obj, args);
    }

invoke方法的逻辑大概分为两部分,第一部分是进行检查(checkAccess),第二部分是通过MethodAccessor调用invoke方法。

检查部分自不必说,做检查一定比不做检查慢。

其实还进行了参数检查,看看传入参数的类型是否合法等。这部分实现在MethodAccessor中,感兴趣的朋友可以去查一查

而第二部分的MethodAccessor是一个接口,有两种实现。第一种是native实现,第二种是Java实现。native方法启动快,但是无法做任何运行时优化。Java实现一开始会慢(因为是字节码),但是变成热点代码以后会被JIT编译器优化。

变成热点代码需要被访问多次,以前是被访问16次。

证明方法:
上文中的结论是两个方法都被调用1000000次,如果我们每个方法调用10次呢?

reflect: 292124 per call
direct: 28570 per call

100次呢?

reflect: 85765 per call
direct: 23214 per call

1000

reflect: 26987 per call
direct: 3685 per call

10000

reflect: 6027 per call
direct: 2422 per call

二者的差距越来越小,说明二者都会被JIT编译器很好的优化,当运行次数趋于无穷大的时候,二者速度将会无穷接近。(甚至可能反超)

比如,运行一千万次

reflect: 1285 per call
direct: 1412 per call

运行一亿次

reflect: 1474 per call
direct: 1839 per call

结论

反射是一种很有用的工具,可以提供良好的扩展性。
反射会带来一定的性能问题,但对于长期运行的程序(如web服务),反射的性能不会成为瓶颈。这是JIT带来的能力。

彩蛋

MethodHanlde类有一个反反射方法,可以对反射方法进行超级优化
刚才的方法跑10000次的情况下:

reflect: 5385 per call
direct: 1913 per call
unreflect: 1818 per call

反反射真是太可怕了。
最后给出代码供大家测试

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;

public class TestClassLoad {
    public static void main(String[] args) throws Throwable {
        Class<?> clz = Class.forName("A");
        Object o = clz.newInstance();
        Method foo = clz.getMethod("foo", String.class);
        int callTimes = 10000;
        A a = new A();
        long startTime = System.nanoTime();
        for (int i=0; i<callTimes; i++) {
            foo.invoke(o, Integer.toString(i));
        }
        long endTime = System.nanoTime();
        String str1 = "reflect: " + (endTime - startTime) / callTimes + " per call";


        startTime = System.nanoTime();
        for (int i=0; i<callTimes; i++) {
            a.foo(Integer.toString(i));
        }
        endTime = System.nanoTime();
        String str2 = "direct: " + (endTime - startTime) / callTimes + " per call";

        MethodHandles.Lookup lookup = MethodHandles.lookup();
        MethodHandle unreflectFoo = lookup.unreflect(foo);
        startTime = System.nanoTime();
        for (int i=0; i<callTimes; i++) {
            unreflectFoo.invoke(o, Integer.toString(i));
        }
        endTime = System.nanoTime();
        String str3 = "unreflect: " + (endTime - startTime) / callTimes + " per call";


        System.out.println(str1);
        System.out.println(str2);
        System.out.println(str3);
    }
}

引用

R大
也是我很喜欢的作者的文章,他很擅长编程语言方面的内容

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值