[多线程系列]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();

  

  

原文地址:https://www.cnblogs.com/coldridgeValley/p/5150696.html