Unsafe類學(xué)習(xí)筆記
Unsafe 類初識(shí)
- Unsafe位于sun.misc包內(nèi)规肴,看其命名就知道和注重安全性的java jdk無緣捶闸,連文檔都沒,直接就叫‘不安全’ 拖刃。Unsafe的特點(diǎn)是可以直接操作堆外內(nèi)存删壮,可以隨意查看及修改JVM中運(yùn)行時(shí)的數(shù)據(jù)結(jié)構(gòu),例如查看和修改對(duì)象的成員兑牡,Unsafe的操作粒度不是類央碟,而是數(shù)據(jù)和地址。
- 如何獲得Unsafe對(duì)象均函,Unsafe類里面可以看到有一個(gè)getUnsafe方法:
@CallerSensitive
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
if(!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
但是很遺憾我們并不能直接調(diào)用亿虽,因?yàn)檫@個(gè)getUnsafe方法會(huì)判斷當(dāng)前調(diào)用這個(gè)方法的對(duì)象的類型,如果并非 java.util.concurrent.atomic內(nèi)的原子類苞也、AbstractQueuedSynchronizer等類型洛勉,則會(huì)拋出SecurityException不安全異常。因此需要反射來調(diào)用這個(gè)方法從而獲得Unsafe對(duì)象實(shí)例:
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;
}
對(duì)于scala如迟,也用的sun.misc.Unsafe收毫, 不過它也用反射封裝了一下攻走,可以直接
scala.concurrent.util.Unsafe unsafe = scala.concurrent.util.Unsafe.instance
調(diào)用
Unsafe類的成員
- 除了上面談及的getUnsafe會(huì)返回Unsafe實(shí)例theUnsafe外,Unsafe一共由105個(gè)方法組成此再,大部分都是native方法昔搂。下面是一些可能用到的方法:
- 返回低級(jí)別內(nèi)存信息
addressSize()
pageSize()
- 手動(dòng)獲得對(duì)象和對(duì)象方法
allocateInstance() 避開構(gòu)造方法生成對(duì)象
objectFieldOffset() 獲得對(duì)象的某個(gè)成員的地址偏移量
- 手動(dòng)獲得類或者靜態(tài)成員
staticFieldOffset() 獲得某個(gè)靜態(tài)成員的地址偏移量
defineClass()
defineAnonymousClass()
ensureClassInitialized()
- 手動(dòng)獲得數(shù)組
arrayBaseOffset()
arrayIndexScale()
- 同步的低級(jí)別基本方法
monitorEnter()
tryMonitorEnter()
monitorExit()
compareAndSwapInt()
putOrderedInt()
- 手動(dòng)操作內(nèi)存
allocateMemory()
copyMemory()
freeMemory()
getAddress()
getInt() ,getInt(Object var1, long var2)第一個(gè)參數(shù)是要get的對(duì)象引润,第二個(gè)參數(shù)是字段的偏移量
putInt()
- 阻塞和喚醒
pack()
unpack()
Unsafe常用方式
- 避開構(gòu)造方法初始化對(duì)象巩趁,使用allocateInstance
Unsafe unsafe = getUnsafe();
final Class aClass = A.class;
A a = (A) unsafe.allocateInstance(aClass);
- 修改對(duì)象成員值(內(nèi)存出錯(cuò)),使用putInt()
A a = new A(12);
Field f = A.class.getDeclaredField("num");
unsafe.putInt(a, unsafe.objectFieldOffset(f), 8);
- 淺復(fù)制
/**
* 將對(duì)象轉(zhuǎn)化成地址
* @param obj
* @return
*/
private static long toAddress(Object obj) {
Object[] objects = new Object[]{obj};
long baseOffset = getUnsafe().arrayBaseOffset(objects.getClass());
return normalize(getUnsafe().getInt(objects, baseOffset));
}
/**
* 將地址轉(zhuǎn)化成對(duì)象
* @param address
* @return
*/
private static Object fromAddress(long address) {
Object[] objects = new Object[]{null};
long baseOffset = getUnsafe().arrayBaseOffset(objects.getClass());
getUnsafe().putLong(objects, baseOffset, address);
return objects[0];
}
private static long normalize(int value) {
if (value > 0) {
return value;
}
return (~0L >>> 32) & value;
}
public static Object shallowCopy(Object obj) {
long size = sizeOf(obj);//對(duì)象所需內(nèi)存大小
long start = toAddress(obj); //對(duì)象的地址起始偏移量
long address = getUnsafe().allocateMemory(size);//分配size大小的內(nèi)存淳附,返回內(nèi)存空間地址偏移量议慰,準(zhǔn)備放入復(fù)制的對(duì)象
getUnsafe().copyMemory(start, address, size);//從對(duì)象地址起始偏移量開始復(fù)制內(nèi)存到address開始的內(nèi)存
return fromAddress(address);//將地址轉(zhuǎn)化成對(duì)象
}
- 實(shí)現(xiàn)多繼承,將兩個(gè)對(duì)象的內(nèi)存空間合并奴曙,得到地址轉(zhuǎn)換成對(duì)象别凹;
- 動(dòng)態(tài)生成類,動(dòng)態(tài)代理庫cglib的原理洽糟,使用defineClass方法;
- 拋出異常:unsafe.throwException(new IOException());
- 快速序列化和反序列化:
序列化:使用getLong, getInt, getObject等方法炉菲;
反序列化:首先使用allocateInstance生成對(duì)象,然后使用putLong, putInt, putObject等方法坤溃,填充對(duì)象拍霜; - 并發(fā)操作:CAS(compareAndSwap)的方法包括
compareAndSwapObject(Object obj, long offset, Object expect, Object update);
compareAndSwapInt(Object obj, long offset, int expect, int update);
compareAndSwapLong(Object obj, long offset, long expect, long update);
CAS是原子操作,能夠用于實(shí)現(xiàn)高性能的線程安全的無鎖數(shù)據(jù)結(jié)構(gòu)薪介,對(duì)沒錯(cuò)祠饺,atomic包內(nèi)的原子類都是實(shí)現(xiàn)的CAS,下面會(huì)詳細(xì)分析汁政。
java一些使用Unsafe的類
- AbstractQueuedSynchronizer即同步器的抽象類道偷,里面實(shí)現(xiàn)了線程的阻塞和喚醒:
LockSupport.park(this)和LockSupport.unpark(this)用于阻塞和喚醒線程。 - 如何去使用CAS實(shí)現(xiàn)一個(gè)線程安全的數(shù)據(jù)結(jié)構(gòu)(類)记劈,直接上代碼:
public class AtomicCounter implements Counter{
private volatile long counter = 0;
private Unsafe unsafe;
private long offset;
public AtomicCounter() throws NoSuchFieldException {
unsafe = UnsafeUtils.getUnsafe();
//獲得counter的地址偏移量
offset = unsafe.objectFieldOffset(AtomicCounter.class.getDeclaredField("counter"));
}
@Override
public void increment() {
long expect = counter;
while (!unsafe.compareAndSwapLong(this, offset, expect, expect + 1)) {
expect = counter;//此時(shí)counter內(nèi)存值為expect + 1
}
}
@Override
public long getCounter() {
return this.counter;
}
}
很好奇compareAndSwapLong到底做了什么操作勺鸦,首先分析一下這個(gè)方法的參數(shù)
compareAndSwapLong(Object obj, long offset, long expect, long update)
從參數(shù)表述可以看出,每次修改變量值之前都會(huì)比較當(dāng)前實(shí)際值和預(yù)期值是否一致目木,只有一致才會(huì)執(zhí)行值修改换途,否則do nothing。
注意到counter聲明為volatile刽射,這意味著counter的值每次都是在內(nèi)存中取军拟,再看increment方法,每次修改值之前會(huì)設(shè)置當(dāng)前字段值為預(yù)期值柄冲,并保證在while循環(huán)中,counter值每次取都和預(yù)期值相同才會(huì)執(zhí)行忠蝗。
參考
http://mishadoff.com/blog/java-magic-part-4-sun-dot-misc-dot-unsafe/