java 反射原理(jvm是如何实现反射的) 您所在的位置:网站首页 java反射的实现方式 java 反射原理(jvm是如何实现反射的)

java 反射原理(jvm是如何实现反射的)

2023-02-03 22:40| 来源: 网络整理| 查看: 265

反射是Java 中非常重要的特性,它允许正在运行的Java程序观测,甚至是修改程序的动态行为。 例如:我们可以通过Class 对象枚举出该类所有方法,我们还可以通过Method.Accessible 绕过Java 语言的访问权限,在私有的方法所在类之外的地方调用该方法。

在Java 开发环境(IDE)中当我们输入对象后输入点号时,编译器会根据点号前的数据动态的展示出对象中的属性和方法。

反射.jpg

在Web开发中,我们经常使用的各种通用框架为了保证框架的可扩展性,往往都使用Java反射功能,根据配置文件中的信息来动态的加载不同的类,还可以为类中的属性赋值等等。例如Spring的IOC功能,底层原理就是使用反射机制。

虽然反射机制很灵活,但是在性能上却带来了一些开销,接下来我们就了解一下反射机制,并看看为什么会有性能问题。

原理

我们根据方法的反射调用来分析下源码,看看Method.invoke是如何实现的。

@CallerSensitive public Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { if (!override) { if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) { Class caller = Reflection.getCallerClass(); checkAccess(caller, clazz, obj, modifiers); } } MethodAccessor ma = methodAccessor; // read volatile if (ma == null) { ma = acquireMethodAccessor(); } return ma.invoke(obj, args); }

通过源码可以看到其实invoke方法实际上是委派给了MethodAccessor来处理,MethodAccessor是一个接口,有两个具体实现方法(methodAccessorImpl 是一个抽象的实现方法另外两个实现对象继承此对象),委托实现和本地实现。从代码中可以看到第一次调用时本地methodAccessor是空,所以会调用acquireMethodAccessor()方法。

image.png

接下来看下获取MethodAccessor实现方法,首先检查是否已经创建,如果创建了就使用创建的,如果没有创建就调用工厂方法创建一个

private MethodAccessor acquireMethodAccessor() { // 首先检查是否已经创建了实现,如果创建了就使用创建的,如果没有就地要用工厂方法创建一个 MethodAccessor tmp = null; if (root != null) tmp = root.getMethodAccessor(); if (tmp != null) { methodAccessor = tmp; } else { // Otherwise fabricate one and propagate it up to the root tmp = reflectionFactory.newMethodAccessor(this); setMethodAccessor(tmp); } return tmp; }

看下反射工厂的newMethodAccessor 方法,从下面可以看到先是检查初始化,然后判断是否开启动态代理实现,如果开启了就会使用动态实现方式(直接生成字节码方式),如果没有开启就会生成一个委派实现,委派实现的具体实现是使用本地实现来完成。

public MethodAccessor newMethodAccessor(Method var1) { checkInitted(); if (noInflation && !ReflectUtil.isVMAnonymousClass(var1.getDeclaringClass())) { return (new MethodAccessorGenerator()).generateMethod(var1.getDeclaringClass(), var1.getName(), var1.getParameterTypes(), var1.getReturnType(), var1.getExceptionTypes(), var1.getModifiers()); } else { NativeMethodAccessorImpl var2 = new NativeMethodAccessorImpl(var1); DelegatingMethodAccessorImpl var3 = new DelegatingMethodAccessorImpl(var2); var2.setParent(var3); return var3; } }

看到这里可能会有一个疑问,为什么使用委派实现穿插在中间,这是因为Java反射实现机制还有一种动态生成字节码,通过invoke指令直接调用目标的方法,委派实现是为了在动态实现和本地实现之间进行切换。

动态实现和本地实现相比,执行速度要快上20倍,这是因为动态实现直接执行字节码,不用从java到c++ 再到java 的转换,但是因为生成字节码的操作比较耗费时间,所以如果仅一次调用的话反而是本地时间快3到4倍。

为了防止很多反射调用只调用一次,java 虚拟机设置了一个阀值等于15(通过-Dsun.reflect.inflationThreshold 参数来调整),当一个反射调用次数达到15次时,委派实现的委派对象由本地实现转换为动态实现,这个过程称之为Inflation。

反射调用的Inflation机制可以通过参数(-Dsun.reflect.noInflation=true)来关闭(对应代码是newMethodAccessor 方法中的if 判断)。这样在反射调用开始的时候就会直接使用动态实现,而不会使用委派实现或者本地实现。

反射调用的性能开销

接下来我们就来看下反射调用的性能开销,在反射调用方法的例子中,我们先后调用了Class.forName,Class.getMethod,以及Method.invoke 三个操作。其中Class.forName 会调用本地方法,Class.getMethod 会遍历该类的公有方法。如果没有匹配到它还会遍历父级的公有方法,可以知道这两个操作非常耗费时间。

值得注意的是,以getMethod 方法为代表的查询操作,会返回一份查询结果的拷贝信息。因此我们避免在热点代码中使用返回Method数组的getMethods 或者getDeclareMethods方法,以减少不必要的堆空间的消耗。

在实际的开发中 ,我们通常会在应用程序中缓存Class.forName 和 Class.getMethod 的结果。因为下面我们就针对Method.invoke 反射调用的性能开销进行分析。

反射功能使用

获取Class 对象

使用静态方法Class.forName 调用对象的getClass() 方法 直接类名 + “.class” 访问

[1]https://www.jianshu.com/p/4b98cd4af198



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有