做网站的公司地址,网站设计模板是什么,为什么做织梦网站时图片出不来,男女做那个视频网站目录
JVM是如何实现反射的反射的性能开销体现在哪里如何优化反射性能开销
1. JVM是如何实现反射的?
反射是Java语言中的一种强大功能#xff0c;它允许程序在运行时动态地获取类的信息以及操作对象。下面是一个简单的示例#xff0c;演示了如何使用反射调用方法#xff…目录
JVM是如何实现反射的反射的性能开销体现在哪里如何优化反射性能开销
1. JVM是如何实现反射的?
反射是Java语言中的一种强大功能它允许程序在运行时动态地获取类的信息以及操作对象。下面是一个简单的示例演示了如何使用反射调用方法
public class Solution {public static void show(int i) {new Exception(# i).printStackTrace();}public static void main(String[] args) throws Exception {Class? clazz Class.forName(Solution);Method method clazz.getMethod(show, int.class);method.invoke(null, 0);}
}在上述代码中我们使用Method.invoke来执行反射方法调用并通过打印show方法的栈轨迹来观察调用的类。输出如下
java.lang.Exception: #0at Solution.show(Solution.java:15)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:498)at Solution.main(Solution.java:21)首先我们看一下Method.invoke的实现
public final class Method extends Executable {...public Object invoke(Object obj, Object... args) throws ... {... // 权限检查MethodAccessor ma methodAccessor;if (ma null) {ma acquireMethodAccessor();}return ma.invoke(obj, args);}
}可以看到实际上它是委派给了MethodAccessor来处理。MethodAccessor是一个接口具有两个具体实现一个是通过本地方法NativeMethodAccessorImpl来实现的称为本地实现另一个是使用了委派模式DelegatingMethodAccessorImpl称为委派实现。
MethodAccessor实例的创建
MethodAccessor实例是在ReflectionFactory中创建的
public class ReflectionFactory {...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;}}
}在第一次调用反射时noInflation为false这时会生成一个委派实现而委派实现的具体实现便是一个本地实现。反射调用在进入Java虚拟机内部后实际是调用目标方法的具体地址。
动态生成字节码的实现
Java的反射调用机制还设立了另一种动态生成字节码的实现简称动态实现直接使用invoke指令来调用目标方法。动态实现的运行效率要快20倍因为它避免了Java到C再到Java的切换但由于生成字节码非常耗时仅调用一次的话本地实现反而要快3到4倍。
Java虚拟机设置了一个阈值15当某个反射调用的次数在15之下时采用本地实现当达到15时开始动态生成字节码并切换至动态实现这个过程称为Inflation。
Inflation机制
NativeMethodAccessorImpl中每次invoke方法被调用时都会增加一次计时器并判断是否超过阈值超过后调用MethodAccessorGenerator.generateMethod()生成Java版的MethodAccessor实现类并改变DelegatingMethodAccessorImpl所引用的MethodAccessor为Java版。
小结
在默认情况下方法的反射调用为委派实现调用超过15次后委派实现便会切换至动态实现该动态实现的字节码是自动生成的将直接使用invoke指令调用目标方法。可以通过参数-Dsun.reflect.noInflationtrue来关闭Inflation机制直接生成动态实现。 2. 反射的性能开销体现在哪里?
在上面的例子中我们使用了Class.forName、Class.getMethod以及Method.invoke三个操作。其中Class.forName调用本地方法Class.getMethod则遍历该类的公有方法如果没有匹配到还将遍历父类的公有方法。这两个操作都是非常耗时的。
需要注意的是以getMethod为代表的查找方法操作会返回查找结果的一份拷贝。因此应避免在热点代码中使用返回Method数组的getMethods或getDeclaredMethods方法以减少不必要的堆空间消耗。
在实践中通常会在应用程序中缓存Class.forName和Class.getMethod的结果因此下面我们只关注反射调用本身的性能开销。
public class ReflectDemo {public void doSth(int i) {}public static void main(String[] args) throws Exception {Class? clazz ReflectDemo.class;Constructor constructor clazz.getConstructor();Object object constructor.newInstance();Method method clazz.getMethod(doSth, int.class);ReflectDemo demo new ReflectDemo();long current System.currentTimeMillis();for (int i 1; i 2_000_000_000; i) {if (i % 100_000_000 0) {long temp System.currentTimeMillis();System.out.println(temp - current);current temp;}// 直接调用demo.doSth(2333);// 反射调用// method.invoke(object, 2333);}}
}根据测试结果一亿次的直接调用耗时为91.6ms而反射调用耗时281.6ms约为基准值的3.07倍。
性能开销来源
反射调用的性能开销主要来自以下几个方面
方法表查找构建Object数组以及可能存在的自动装拆箱操作运行时权限检查可能没有方法内联/逃逸分析 3. 如何优化反射性能开销?
反射性能优化策略
尽量避免反射调用虚方法虚方法调用的性能开销更大。关闭运行时权限检查使用setAccessible(true)可以提升性能。扩大基本数据类型对应的包装类缓存可通过参数-Djava.lang.Integer.IntegerCache.high128来实现。关闭Inflation机制直接动态生成字节码。提高JVM关于每个调用能够记录的类型数目通过虚拟机参数-XX:TypeProfileWidth设置更大的值。
通过上述方法可以有效减少反射调用的性能开销提高程序的整体性能。在实际开发中根据具体场景灵活应用这些优化策略可以显著提升反射操作的效率。