在分析atomic包的时候看到很多类的静态代码块中使用了一下这个方法(例如AtomicInteger)
static { try { valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } }
所以对objectFieldOffset方法很好奇,看了注释也没太了解,搜索了相关资料,查询到一个比较好的答复,链接在此
RednaxelaFX 2013-06-03sun.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();