公司转正答辩的时候被问到,在利用反射的时候,如果并发量比较大的情况下,如何进行优化,当时比较紧张,大脑一片空白,时候回来查找了一些资料,在这里做一些总结
首先先了解一下,java的反射为什么慢
反射Field/get
跟着源码看下,最后是使用FieldAccessor来获取的:
都是写使用Unsafe类来访问的,Unsafe类是Java中可以像C语言中那样使用指针偏移来操作Java对象(还有并发CAS等)的一个工具类,这个类的实现是JNI的C++代码:
但是JNI毕竟是JNI,这让JVM无法预知它的行为带来的影响,本来可以有的很多优化被此JNI调用给隔绝了,而Java本来就是靠动态优化吃饭的(Java是门半编译型半解释型语言,不像C++靠编译优化),所以性能影响还是蛮大的。
所以:
1.由于是本地方法调用,让JVM无法优化(还有JIT?)。
2.反射方法调用还有验证过程和参数问题,参数需要装箱拆箱、需要组装成Object[]形式、异常的包装等等问题,篇幅问题这里不加以叙述。
那么针对以上可以有以下的优化方案:
1、setA
ccessible(true)
使用了method.setAccessible(true)后 性能有了20倍的提升,
实际上setAccessible是启用和禁用访问安全检查的开关,并不是为true就能访问为false就不能访问,
由于JDK的安全检查耗时较多.所以通过setAccessible(true)的方式关闭安全检查就可以达到提升反射速度的目的
JDK API中的解释 :
AccessibleObject 类是 Field、Method 和 Constructor 对象的基类。它提供了将反射的对象标记为在使用时取消默认 Java 语言访问控制检查的能力。对于公共成员、默认(打包)访问成员、受保护成员和私有成员,在分别使用 Field、Method 或 Constructor 对象来设置或获得字段、调用方法,或者创建和初始化类的新实例的时候,会执行访问检查。
在反射对象中设置 accessible 标志允许具有足够特权的复杂应用程序(比如 Java Object Serialization 或其他持久性机制)以某种通常禁止使用的方式来操作对象。
2、jit,不去了解反射优化,还真不知道JIT是什么东西
如果你在jdk6上跑,且如果你反射的目标方法是getter/setter methods的话,记得加上配置:-XX:-UseFastEmptyMethods -XX:-UseFastAccessorMethods , 这两个配置的关闭是为了让accessor methods能够被jit; jdk7以上不需要设置这两个配置
即时编译,又译及时编译、实时编译,动态编译的一种形式,是一种提高程序运行效率的方法。通常,程序有两种运行方式:静态编译与动态直译。静态编译的程序在执行前全部被翻译为机器码,而直译执行的则是一句一句边运行边翻译。 即时编译器则混合了这二者,一句一句编译源代码,但是会将翻译过的代码缓存起来以降低性能损耗。相对于静态编译代码,即时编译的代码可以处理延迟绑定并增强安全性。 即时编译器有两种类型,一是字节码翻译,二是动态编译翻译。 微软的.NET Framework,还有绝大多数的Java实现,都依赖即时翻译以提供高速的代码执行
如果你在jdk6上跑,且如果你反射的目标方法是getter/setter methods的话,记得加上配置:-XX:-UseFastEmptyMethods -XX:-UseFastAccessorMethods , 这两个配置的关闭是为了让accessor methods能够被jit; jdk7以上不需要设置这两个配置
3、缓存
这个优化是一般反射优化的基本解决方案,就是把所有经常用到的反射对象缓存起来,在下次用到的时候直接从缓存中获取
- 系统启动阶段使用反射。
- 将反射得到元数据保存起来,使用时,只需从内存中调用即可。
- hotspot虚拟机会对执行次数较多的方法进行优化(例如使用jit技术)
4、使用高性能的反射类库ReflectASM
为什么ReflectASM比jdk的反射要快呢?
原理
- public class User {
- private int id;
- private String name;
- public int getId() {
- return id;
- }
- public void setId(int id) {
- this.id = id;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- }
- public class ReflectAsmTest {
- public static void main(String[] args) {
- User user = new User();
- //使用reflectasm生产User访问类
- MethodAccess access = MethodAccess.get(User.class);
- //invoke setName方法name值
- access.invoke(user, "setName", "张三");
- //invoke getName方法 获得值
- String name = (String)access.invoke(user, "getName", null);
- System.out.println(name);
- }
- }
看了下源码,这段代码主要是通过asm(一种汇编语言)生产一个User的处理类 UserMethodAccess(这个类主要是实现了invoke方法)的ByteCode,然后获得该对象,通过上面的invoke操作user类。
- // Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
- // Jad home page: http://www.kpdus.com/jad.html
- // Decompiler options: packimports(3)
- package com.johhny.ra;
- import com.esotericsoftware.reflectasm.MethodAccess;
- // Referenced classes of package com.johhny.ra:
- // User
- public class UserMethodAccess extends MethodAccess
- {
- public UserMethodAccess()
- {
- }
- /**
- * 这个方法是主要是实现了MethodAccess 的抽象方法,来实现反射的功能
- * @param obj 需要反射的对象
- * @param i class.getDeclaredMethods 对应方法的index
- * @param 参数对象集合
- * @return
- */
- public transient Object invoke(Object obj, int i, Object aobj[])
- {
- User user = (User)obj;
- switch(i)
- {
- case 0: // '\0'
- return user.getName();
- case 1: // '\001'
- return Integer.valueOf(user.getId());
- case 2: // '\002'
- user.setName((String)aobj[0]);
- return null;
- case 3: // '\003'
- user.setId(((Integer)aobj[0]).intValue());
- return null;
- }
- throw new IllegalArgumentException((new StringBuilder("Method not found: ")).append(i).toString());
- }
- }
看了UserMethodAccess源码后明白 ReflectASM 为什么会比java放射快那么多,其实就是我们的bean调用里面的方法,速度当然很快
注意:
1. MethodAccess.get()方法比较耗时的,特别是类方法比较多的时候,如果生成的反射类用到的地方比较多或者会多次调用,建议缓存下来,如果使用次数很少建议还是使用反射来完成功能