开发过程中,不少情况下都会遇到需要通过反射修改对象字段值的情况,但是很多情况下,直接反射效率比较低,而你如果读过jdk代码,可能多少会发现jdk里边经常闪现这UNSAFE的的身影,比如ConcurrentLinkedQueue里边到处都是。
为啥jdk里边到处都用这个东西呢?YY下,一方面是因为效率高,另一方面是因为它提供了很强大底层操作的功能,比如不用AtomicObject就可以直接支持cas操作修改对象字段,可以直接申请释放内存。
调用效率高,到底有多高呢?直接上代码:
import sun.misc.Unsafe; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * -Xcomp * @author tianmai.fh * @date 2014-03-12 16:43 */ public class FieldOffsetTest { static int COUNT = 50000000; static int LENGTH = 1024; static long BEAN_A_OFFSET; static Unsafe unsafe; static Field aField; static Field bField; static long BEAN_B_OFFSET; static { try { Field field = sun.misc.Unsafe.class.getDeclaredField("theUnsafe"); field.setAccessible(true); unsafe = (Unsafe) field.get(null); Method objectFieldOffsetMethod = Unsafe.class.getDeclaredMethod("objectFieldOffset", new Class[]{Field.class}); objectFieldOffsetMethod.setAccessible(true); BEAN_A_OFFSET = unsafe.objectFieldOffset(Bean.class.getDeclaredField("a")); BEAN_B_OFFSET = unsafe.objectFieldOffset(Bean.class.getDeclaredField("b")); aField = Bean.class.getDeclaredField("a"); aField.setAccessible(true); bField = Bean.class.getDeclaredField("b"); bField.setAccessible(true); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } static class Bean { private int a; private int b; private int c[] = new int[LENGTH]; } public static void testFieldOffsetAssign() throws NoSuchFieldException, InvocationTargetException, IllegalAccessException { Bean bean = new Bean(); long start = System.currentTimeMillis(); for (int i = 0; i < COUNT; i++) { unsafe.putInt(bean,BEAN_A_OFFSET,i); unsafe.putInt(bean,BEAN_B_OFFSET,i); } long end = System.currentTimeMillis(); System.out.println("Offset spend: " + (end - start) + " ms"); } public static void testReflectAssign() throws IllegalAccessException { Bean bean = new Bean(); long start = System.currentTimeMillis(); for (int i = 0; i < COUNT; i++) { aField.set(bean, i); bField.set(bean, i); } long end = System.currentTimeMillis(); System.out.println("Reflect spend: " + (end - start) + " ms"); } public static void testDirectAssign() throws IllegalAccessException { Bean bean = new Bean(); long start = System.currentTimeMillis(); for (int i = 0; i < COUNT; i++) { bean.a = i; bean.b = i; } long end = System.currentTimeMillis(); System.out.println("Direct spend: " + (end - start) + " ms"); } public static void main(String[] args) throws NoSuchFieldException, InvocationTargetException, IllegalAccessException { for (int i = 0; i < 5; i++) { testDirectAssign(); testReflectAssign(); testFieldOffsetAssign(); System.out.println(); } } }
看输出结果:
Direct spend: 8 ms Reflect spend: 2111 ms Offset spend: 84 ms Direct spend: 0 ms Reflect spend: 2163 ms Offset spend: 81 ms Direct spend: 0 ms Reflect spend: 2134 ms Offset spend: 83 ms Direct spend: 0 ms Reflect spend: 2097 ms Offset spend: 80 ms Direct spend: 0 ms Reflect spend: 2150 ms Offset spend: 79 ms
通过unsafe赋值耗费的时间是反射调用耗费时间的4%,一个数量级之差!当然在可以直接调用的时候呢,还是直接调用好了,效率更高!
细读下代码,应用代码不能直接获取Unsafe实例的:
public static Unsafe getUnsafe() { Class cc = sun.reflect.Reflection.getCallerClass(2); if (cc.getClassLoader() != null) throw new SecurityException("Unsafe"); return theUnsafe; }
这个类的注释上写只有信任代码才能获取这个类的实例,从源码看呢,就是BootstrapClassLoader加载的类才可以使用,也就是jdk的核心类库才可以使用它,所以我demo中就通过反射获取了。当然如果jdk升级,这个类消失或者接口变动,只有自己负责了。
A collection of methods for performing low-level, unsafe operations.Although the class and all methods are public, use of this class is limited because only trusted code can obtain instances of it.
通过Unsafe的接口可以获得字段在类中的偏移量,这个偏移量可以用来在对象上找到字段相对于对象地址的偏移量,对象在堆上也只是一块内存而已,有了对象地址作为起始地址,加上偏移量,很容易获取到字段在堆中的位置了,而且这个偏移量不会因为gc移动对象实例位置而变动:
public native long objectFieldOffset(Field f)
剩下的就只有使用了!enjoy it !
相关推荐
最明显的就是类似于LAST_UPDATE_TIME了,通常为了保证这个字段的一致性,通常在插入新记录时采用当前数据库时间作为字段值。但IBatisNet接收的实体类对象属性都是普通C#类型,并不具备传入表达式的能力。如果采用...
最明显的就是类似于LAST_UPDATE_TIME了,通常为了保证这个字段的一致性,通常在插入新记录时采用当前数据库时间作为字段值。但IBatisNet接收的实体类对象属性都是普通C#类型,并不具备传入表达式的能力。如果采用...
最明显的就是类似于LAST_UPDATE_TIME了,通常为了保证这个字段的一致性,通常在插入新记录时采用当前数据库时间作为字段值。但IBatisNet接收的实体类对象属性都是普通C#类型,并不具备传入表达式的能力。如果采用...
采用Model+DAL +BLL+Web 的设计,主要实现在对应数据库中表的基类代码的自动生成,包括生成属性、添加、修改、删除、查询、存在性、Model 类构造等基础代码片断,支持不同3种架构代码生成,使程序员可以节省大量机械...
通过反射调用類的方法,屬性,字段,索引器(2種方法) ASP.NET: State Server Gems 完整的动态加载/卸载程序集的解决方案 从NUnit中理解.NET自定义属性的应用(转载) 如何在.NET中实现脚本引擎 (CodeDom篇) .NET的插件...