• [多线程系列]unsafe类和反射获取对象字段值速度比较


        在分析atomic包的时候看到很多类的静态代码块中使用了一下这个方法(例如AtomicInteger)

     static {
          try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
          } catch (Exception ex) { throw new Error(ex); }
        }
    

      

    所以对objectFieldOffset方法很好奇,看了注释也没太了解,搜索了相关资料,查询到一个比较好的答复,链接在此

    RednaxelaFX 2013-06-03
    sun.misc.Unsafe是JDK内部用的工具类。它通过暴露一些Java意义上说“不安全”的功能给Java层代码,来让JDK能够更多的使用Java代码来实现一些原本是平台相关的、需要使用native语言(例如C或C++)才可以实现的功能。该类不应该在JDK核心类库之外使用。

    JVM的实现可以自由选择如何实现Java对象的“布局”,也就是在内存里Java对象的各个部分放在哪里,包括对象的实例字段和一些元数据之类。sun.misc.Unsafe里关于对象字段访问的方法把对象布局抽象出来,它提供了objectFieldOffset()方法用于获取某个字段相对Java对象的“起始地址”的偏移量,也提供了getInt、getLong、getObject之类的方法可以使用前面获取的偏移量来访问某个Java对象的某个字段。

    每个JVM都有自己的方式来实现Java对象的布局。Oracle/Sun HotSpot VM所使用的Java对象布局可以参考这篇博客:http://www.codeinstructions.com/2008/12/java-objects-memory-structure.html
    (这篇内容其实并不是太完整但只是入门凑合看看是够了。另外它只针对32位的JDK6的HotSpot VM的默认配置。)

    同事Aleksey Shipilev专门写了个小工具来显示Java对象的布局:
    https://github.com/shipilev/java-object-layout
     

    看完以后恍然大悟,(<深入jvm虚拟机中>有讲解过内存中java对象的结构,2.3.2:对象的内存布局和2.3.3对象的访问定位)

    好奇心起来以后,就在想既然是获取值,为啥不用反射,又查询了一堆资料后,得到的反馈结果是unsafe获取值的速度要比反射要快,于是就有了下面的测试.

    测试环境:

      

    工具:

    JVM配置:

      -Xms1024m -Xmx1024m

    测试代码:

      

    package com.iwjw.learn.thread;
    
    import sun.misc.Unsafe;
    
    import java.lang.reflect.Field;
    
    /**
     * atomic包经常用到的类 测试
     */
    public class UnsafeTest {
    
        private static int testTimes = 10000;
        private static Field ageIntField;
        private static Field nameStringField;
        private static Unsafe unsafe;
        private static Person person = new Person();
    
        static {
            try {
                ageIntField = Person.class.getDeclaredField("age");
                nameStringField = Person.class.getDeclaredField("name");
                unsafe = UnsafeTest.getUnsafeInstance();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        public static void main(String[] args) throws Exception {
            int times = 1000;
            long refTotal = 0L;
            long unsafeTotal = 0L;
            for (int i = 0; i < times; i++) {
                //测试反射获取字段值 需要的时间
                refTotal += testReflection();
                //测试unsafe类获取字段值需要的时间
                unsafeTotal += testUnsafe();
            }
            System.out.println("reflection cost total msec:" + (refTotal));
            System.out.println("unsafe cost total msec:" + (unsafeTotal));
        }
    
        /**
         * 测试unsafe 获取字段值
         */
        private static long testUnsafe() {
            long start = System.currentTimeMillis();
            int i = testTimes;
            long ageOffset = unsafe.objectFieldOffset(ageIntField);
            long nameOffset = unsafe.objectFieldOffset(nameStringField);
            while (i-- > 0) {
                unsafe.getInt(person, ageOffset);
                unsafe.getObject(person, nameOffset);
            }
            long end = System.currentTimeMillis();
    //        System.out.println("unsafe " + testTimes + " times cost msec:" + (end - start));
            return end - start;
        }
    
        /**
         * 测试 反射  获取字段值
         */
        private static long testReflection() {
            long start = System.currentTimeMillis();
            int i = testTimes;
            nameStringField.setAccessible(true);
            ageIntField.setAccessible(true);
            while (i-- > 0) {
                try {
                    ageIntField.getInt(person);
                    nameStringField.get(person);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
            long end = System.currentTimeMillis();
    //        System.out.println("reflection " + testTimes + " times cost msec:" + (end - start));
            return end - start;
        }
    
        /**
         * 获取 unsafe实例
         *
         * @return
         * @throws Exception
         */
        private static Unsafe getUnsafeInstance() throws Exception {
            /*
                Unsafe unsafe =  Unsafe.getUnsafe()
                atomic包中使用该方法获取unsafe实例,但是在非jdk环境中,
                使用这样的方式获取实例使用会报错 java.lang.RuntimeException. java.lang.SecurityException
             */
            //通过观察 Unsafe 类中存在字段theUnsafe 通过反射来获取 Unsafe 实例
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            return (Unsafe) field.get(Unsafe.class);
        }
    
    }
    
    class Person {
        private int age = 10;
        private String name = "谢谢";
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }
    

     

    测试结果:

    类型消耗的总时间(微秒)获取次数 100 1000 10000 100000 1000000 10000000
    reflection 10 16 57 463 4463 43891
    unsafe 4 5 12 41 347 3375

        

    结论: 我测试了很多次,结果差异很大,但是有个总的趋势就是,unsafe获取对象字段值的速度的确要快于反射,但是在一定数量的级别之前基本没区别.但是通过测试代码可以发现unsafe是jdk内部使用的类,反射是可以给外部使用的类,

           在个人编码的时候 请别使用unsafe!!!


    ps: 关于两种方式获取字段的速度我个人想法如下

         unsafe是先根据class对象获取一个字段相对于一个对象的固定偏移量(一个字段在初始化的时候,相对与内存的地址的偏移量都是固定的,可以参见<深入jvm虚拟机>),然后再从一个对象中根据偏移量来获取值

         ......

      在我准备写反射的原理的时候,我去查看了下源码,点进去看看反射是怎么实现的!

      

     1    /**
     2      * Gets the value of a static or instance field of type
     3      * {@code int} or of another primitive type convertible to
     4      * type {@code int} via a widening conversion.
     5      *
     6      * @param obj the object to extract the {@code int} value
     7      * from
     8      * @return the value of the field converted to type {@code int}
     9      *
    10      * @exception IllegalAccessException    if this {@code Field} object
    11      *              is enforcing Java language access control and the underlying
    12      *              field is inaccessible.
    13      * @exception IllegalArgumentException  if the specified object is not
    14      *              an instance of the class or interface declaring the
    15      *              underlying field (or a subclass or implementor
    16      *              thereof), or if the field value cannot be
    17      *              converted to the type {@code int} by a
    18      *              widening conversion.
    19      * @exception NullPointerException      if the specified object is null
    20      *              and the field is an instance field.
    21      * @exception ExceptionInInitializerError if the initialization provoked
    22      *              by this method fails.
    23      * @see       Field#get
    24      */
    25     @CallerSensitive
    26     public int getInt(Object obj)
    27         throws IllegalArgumentException, IllegalAccessException
    28     {
    29         if (!override) {
    30             if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
    31                 checkAccess(Reflection.getCallerClass(), clazz, obj, modifiers);
    32             }
    33         }
    34         return getFieldAccessor(obj).getInt(obj);
    35     }

    好,我们继续点进getInt

    然后我们再来看看 他的实现

    我觉得应该是这个!!看着就很像哈哈,那继续点进去,不过我已经有不好的预感了,为什么能看到unsafe...

    然后我犹豫了5秒还是点了UnsafeIntegerFieldAccessorImpl....

    然后我就面红耳赤了,原来反射的底层用的是unsafe类实现的,只不过之前要做很多判断,丢人了,其实根本就不需要测试,理论上反射肯定会比unsafe来的慢,唉,还是贴出来吧,警示后人.

     

    最后贴出unsafe类中类似的方法

      

    //获取对象中非静态字段的偏移量(get offset of a non-static field in the object in bytes
    public native long objectFieldOffset(java.lang.reflect.Field field);
    //获取数组中第一个元素的偏移量(get offset of a first element in the array)
    public native int arrayBaseOffset(java.lang.Class aClass);
    //获取数组中一个元素的大小(get size of an element in the array)
    public native int arrayIndexScale(java.lang.Class aClass);
    //获取JVM中的地址值(get address size for your JVM)
    public native int addressSize();
    

      

      

  • 相关阅读:
    DB9 ------ 接口定义
    以太网 ------ Auto-Negotiation(自动协商)
    Qt ------ 添加某个功能,比如(QSerialPort)注意事项
    Modbus
    Centos7.5 安装JDK1.8 步骤
    Kafka 消息中间件
    使用RabbitMQ实现分布式事务
    RabbitMq的环境安装
    RabbitMQ消息中间件的用法
    基于Docker+Jenkins实现自动化部署
  • 原文地址:https://www.cnblogs.com/coldridgeValley/p/5150696.html
Copyright © 2020-2023  润新知