Java反射优化

公司转正答辩的时候被问到,在利用反射的时候,如果并发量比较大的情况下,如何进行优化,当时比较紧张,大脑一片空白,时候回来查找了一些资料,在这里做一些总结

首先先了解一下,java的反射为什么慢

反射Field/get

跟着源码看下,最后是使用FieldAccessor来获取的:


都是写使用Unsafe类来访问的,Unsafe类是Java中可以像C语言中那样使用指针偏移来操作Java对象(还有并发CAS等)的一个工具类,这个类的实现是JNI的C++代码:
[java]  view plain  copy
  1. jlong  
  2. sun::misc::Unsafe::getLong (jobject obj, jlong offset)  
  3. {  
  4.   jlong *addr = (jlong *) ((char *) obj + offset);  
  5.   spinlock lock;  
  6.   return *addr;  
  7. }  

C++中其实就是简单的通过基地址和偏移来指针运算拿到内存值,感觉上没有什么劣势,只有JNI,但Unsafe可是JVM自带的JNI(Intrinsic function?),性能应该不会差。
但是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、缓存
    这个优化是一般反射优化的基本解决方案,就是把所有经常用到的反射对象缓存起来,在下次用到的时候直接从缓存中获取
  1. 系统启动阶段使用反射。
  2. 将反射得到元数据保存起来,使用时,只需从内存中调用即可。
  3. hotspot虚拟机会对执行次数较多的方法进行优化(例如使用jit技术)

4、使用高性能的反射类库ReflectASM
    为什么ReflectASM比jdk的反射要快呢?
    
原理
  1. public class User {  
  2.     private int id;  
  3.     private String name;  
  4.   
  5.     public int getId() {  
  6.         return id;  
  7.     }  
  8.   
  9.     public void setId(int id) {  
  10.         this.id = id;  
  11.     }  
  12.   
  13.     public String getName() {  
  14.         return name;  
  15.     }  
  16.   
  17.     public void setName(String name) {  
  18.         this.name = name;  
  19.     }  
  20. }  
  21.   
  22.   
  23.   
  24. public class ReflectAsmTest {  
  25.     public static void main(String[] args) {  
  26.         User user = new User();  
  27.         //使用reflectasm生产User访问类  
  28.         MethodAccess access = MethodAccess.get(User.class);  
  29.         //invoke setName方法name值  
  30.         access.invoke(user, "setName""张三");  
  31.         //invoke getName方法 获得值  
  32.         String name = (String)access.invoke(user, "getName"null);  
  33.         System.out.println(name);  
  34.     }  

看了下源码,这段代码主要是通过asm(一种汇编语言)生产一个User的处理类 UserMethodAccess(这个类主要是实现了invoke方法)的ByteCode,然后获得该对象,通过上面的invoke操作user类。



  1. // Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.  
  2. // Jad home page: http://www.kpdus.com/jad.html  
  3. // Decompiler options: packimports(3)   
  4.   
  5. package com.johhny.ra;  
  6.   
  7. import com.esotericsoftware.reflectasm.MethodAccess;  
  8.   
  9. // Referenced classes of package com.johhny.ra:  
  10. //            User  
  11.   
  12. public class UserMethodAccess extends MethodAccess  
  13. {  
  14.   
  15.     public UserMethodAccess()  
  16.     {  
  17.     }  
  18.   
  19.     /** 
  20.      * 这个方法是主要是实现了MethodAccess 的抽象方法,来实现反射的功能   
  21.      * @param obj  需要反射的对象 
  22.      * @param i  class.getDeclaredMethods 对应方法的index 
  23.      * @param 参数对象集合 
  24.      * @return 
  25.      */  
  26.     public transient Object invoke(Object obj, int i, Object aobj[])  
  27.     {  
  28.         User user = (User)obj;  
  29.         switch(i)  
  30.         {  
  31.         case 0// '\0'  
  32.             return user.getName();  
  33.   
  34.         case 1// '\001'  
  35.             return Integer.valueOf(user.getId());  
  36.   
  37.         case 2// '\002'  
  38.             user.setName((String)aobj[0]);  
  39.             return null;  
  40.   
  41.         case 3// '\003'  
  42.             user.setId(((Integer)aobj[0]).intValue());  
  43.             return null;  
  44.         }  
  45.         throw new IllegalArgumentException((new StringBuilder("Method not found: ")).append(i).toString());  
  46.     }  
  47. }  
看了UserMethodAccess源码后明白 ReflectASM 为什么会比java放射快那么多,其实就是我们的bean调用里面的方法,速度当然很快

注意:
    1. MethodAccess.get()方法比较耗时的,特别是类方法比较多的时候,如果生成的反射类用到的地方比较多或者会多次调用,建议缓存下来,如果使用次数很少建议还是使用反射来完成功能
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值