简介
Java是一种安全的编程语言,可以防止程序员犯许多愚蠢的错误,其中大多数错误都是基于内存管理的。但是,有一种方法可以绕过这些限制,即使用 Unsafe class。可以手动操作内存,这样可以大大减少垃圾回收时间而且可以减少堆内内存的使用。
获取Unsafe对象
Unsafe类里面可以看到有一个getUnsafe方法:
@CallerSensitive
public static Unsafe getUnsafe() {
Class<?> caller = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(caller.getClassLoader()))
throw new SecurityException("Unsafe");
return theUnsafe;
}
但是直接调用会抛出SecurityException不安全异常,因为这个getUnsafe方法会检查我们的代码是否由BootClassLoader加载了。很明显项目中所写的代码都是由Appclass Loader加载的。所以报错了。
如何做呢?
(1)我们可以使我们的代码“可信”。在运行程序时使用选项bootclasspath,把要使用Unsafe的类添加到系统类路径中。
例如:
java -Xbootclasspath:/usr/jdk1.7.0/jre/lib/rt.jar:. com.duoheshui.com.UnsafeTestClient
但是这样太麻烦了
(2)使用反射:
public static Unsafe getUnsafe() {
try {
Field singletonInstanceField = Unsafe.class.getDeclaredField("theUnsafe");
singletonInstanceField.setAccessible(true);
return (Unsafe) singletonInstanceField.get(null);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
Unsafe类的成员
除了上面谈及的getUnsafe会返回Unsafe实例theUnsafe外,Unsafe一共由105个方法组成,大部分都是native方法。下面是一些可能用到的方法:
返回低级别内存信息
addressSize()
pageSize()
手动获得对象和对象方法
allocateInstance() 避开构造方法生成对象
objectFieldOffset() 获得对象的某个成员的地址偏移量
手动获得类或者静态成员
staticFieldOffset() 获得某个静态成员的地址偏移量
defineClass()
defineAnonymousClass()
ensureClassInitialized()
手动获得数组
arrayBaseOffset()
arrayIndexScale()
同步的低级别基本方法
monitorEnter()
tryMonitorEnter()
monitorExit()
compareAndSwapInt()
putOrderedInt()
手动操作内存
allocateMemory()
copyMemory()
freeMemory()
getAddress()
getInt() ,getInt(Object var1, long var2)第一个参数是要get的对象,第二个参数是字段的偏移量
putInt()
阻塞和唤醒
pack()
unpack()
用法
1、CAS(compareAndSwap)
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;
// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
//获取变量value的地址偏移量
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
//声明为volatile,有变化回写到主内存,其他线程再重新从主内存读取最新的数据,保持可见性
private volatile int value;
//比较并交换
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
sun.misc.Unsafe.compareAndSwapInt(Object, long, int, int)
2、避开构造方法初始化对象,使用allocateInstance
Unsafe unsafe = getUnsafe();
final Class userClass = User.class;
User user = (User) unsafe.allocateInstance(userClass );
3、修改对象成员值,使用putInt()
User a = new User(10);
Field f = User.class.getDeclaredField("age");
unsafe.putInt(a, unsafe.objectFieldOffset(f), 8);
4、获取对象地址
获取部门对象在内存中的地址偏移量
private DepartMent dept;
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("dept"));
CAS(compareAndSwapInt)源码实现
JDK8 src/share/vm/prims/unsafe.cpp
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
UnsafeWrapper("Unsafe_CompareAndSwapInt");
oop p = JNIHandles::resolve(obj);
jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END
将调用Atomic::cmpxchg(x, addr, e)进行对比交换,该方法在hotspot\src\share\vm\runtime\atomic.cpp中
inline jbyte Atomic::cmpxchg(jbyte exchange_value, volatile jbyte* dest,
jbyte compare_value, cmpxchg_memory_order order) {
STATIC_ASSERT(sizeof(jbyte) == 1);
volatile jint* dest_int =
static_cast<volatile jint*>(align_ptr_down(dest, sizeof(jint)));
size_t offset = pointer_delta(dest, dest_int, 1);
jint cur = *dest_int;
jbyte* cur_as_bytes = reinterpret_cast<jbyte*>(&cur);
// current value may not be what we are looking for, so force it
// to that value so the initial cmpxchg will fail if it is different
cur_as_bytes[offset] = compare_value;
// always execute a real cmpxchg so that we get the required memory
// barriers even on initial failure
do {
// value to swap in matches current value ...
jint new_value = cur;
// ... except for the one jbyte we want to update
reinterpret_cast<jbyte*>(&new_value)[offset] = exchange_value;
jint res = cmpxchg(new_value, dest_int, cur, order);
if (res == cur) break; // success
// at least one jbyte in the jint changed value, so update
// our view of the current jint
cur = res;
// if our jbyte is still as cur we loop and try again
} while (cur_as_bytes[offset] == compare_value);
return cur_as_bytes[offset];
}
大意就是先去获取一次结果,如果结果和现在不同,就直接返回,因为有其他人修改了;否则会一直尝试去修改。直到成功。
参考
http://mishadoff.com/blog/java-magic-part-4-sun-dot-misc-dot-unsafe/