Java反射优化

来源:互联网 发布:originlab软件下载 编辑:程序博客网 时间:2024/05/29 07:34

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

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

跟着源码看下,最后是使用FieldAccessor来获取的:
这里写图片描述

都是写使用Unsafe类来访问的,Unsafe类是Java中可以像C语言中那样使用指针偏移来操作Java对象(还有并发CAS等)的一个工具类,这个类的实现是JNI的C++代码:

jlong  sun::misc::Unsafe::getLong (jobject obj, jlong offset)  {    jlong *addr = (jlong *) ((char *) obj + offset);    spinlock lock;    return *addr;  }  

C++中其实就是简单的通过基地址和偏移来指针运算拿到内存值,感觉上没有什么劣势,只有JNI,但Unsafe可是JVM自带的JNI(Intrinsic function?),性能应该不会差。
但是JNI毕竟是JNI,这让JVM无法预知它的行为带来的影响,本来可以有的很多优化被此JNI调用给隔绝了,而Java本来就是靠动态优化吃饭的(Java是门半编译型半解释型语言,不像C++靠编译优化),所以性能影响还是蛮大的。

所以:

1.由于是本地方法调用,让JVM无法优化(还有JIT?)。

2.反射方法调用还有验证过程和参数问题,参数需要装箱拆箱、需要组装成Object[]形式、异常的包装等等问题,篇幅问题这里不加以叙述。

那么针对以上可以有以下的优化方案:

1、setAccessible(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的反射要快呢?

原理

    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()方法比较耗时的,特别是类方法比较多的时候,如果生成的反射类用到的地方比较多或者会多次调用,建议缓存下来,如果使用次数很少建议还是使用反射来完成功能

1 0