簡介
Java是一種安全的編程語言,可以防止程序員犯許多愚蠢的錯誤河爹,其中大多數(shù)錯誤都是基于內(nèi)存管理的匠璧。但是,有一種方法可以繞過這些限制咸这,即使用 Unsafe class夷恍。可以手動操作內(nèi)存媳维,這樣可以大大減少垃圾回收時間而且可以減少堆內(nèi)內(nèi)存的使用酿雪。
獲取Unsafe對象
Unsafe類里面可以看到有一個getUnsafe方法:
@CallerSensitive
public static Unsafe getUnsafe() {
Class<?> caller = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(caller.getClassLoader()))
throw new SecurityException("Unsafe");
return theUnsafe;
}
但是直接調(diào)用會拋出SecurityException不安全異常,因為這個getUnsafe方法會檢查我們的代碼是否由BootClassLoader加載了侄刽。很明顯項目中所寫的代碼都是由Appclass Loader加載的指黎。所以報錯了。
如何做呢州丹?
(1)我們可以使我們的代碼“可信”醋安。在運行程序時使用選項bootclasspath,把要使用Unsafe的類添加到系統(tǒng)類路徑中墓毒。
例如:
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方法所计。下面是一些可能用到的方法:
返回低級別內(nèi)存信息
addressSize()
pageSize()
手動獲得對象和對象方法
allocateInstance() 避開構(gòu)造方法生成對象
objectFieldOffset() 獲得對象的某個成員的地址偏移量
手動獲得類或者靜態(tài)成員
staticFieldOffset() 獲得某個靜態(tài)成員的地址偏移量
defineClass()
defineAnonymousClass()
ensureClassInitialized()
手動獲得數(shù)組
arrayBaseOffset()
arrayIndexScale()
同步的低級別基本方法
monitorEnter()
tryMonitorEnter()
monitorExit()
compareAndSwapInt()
putOrderedInt()
手動操作內(nèi)存
allocateMemory()
copyMemory()
freeMemory()
getAddress()
getInt() 柠辞,getInt(Object var1, long var2)第一個參數(shù)是要get的對象,第二個參數(shù)是字段的偏移量
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叭首,有變化回寫到主內(nèi)存习勤,其他線程再重新從主內(nèi)存讀取最新的數(shù)據(jù),保持可見性
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焙格、避開構(gòu)造方法初始化對象图毕,使用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吴旋、獲取對象地址
獲取部門對象在內(nèi)存中的地址偏移量
private DepartMent dept;
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("dept"));
CAS(compareAndSwapInt)源碼實現(xiàn)
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
將調(diào)用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];
}
大意就是先去獲取一次結(jié)果厢破,如果結(jié)果和現(xiàn)在不同,就直接返回治拿,因為有其他人修改了摩泪;否則會一直嘗試去修改。直到成功劫谅。
參考
http://mishadoff.com/blog/java-magic-part-4-sun-dot-misc-dot-unsafe/