在分析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();