java是一個安全性很高的平臺,開發者在使用過程中,并不需要直接對內存進行操作,Unsafe對象字如其名,是一個不安全的工具類,可以突破java語言的限制,對內存進行操作。
## 初始化
Unsafe的構造方法是私有的,我們不能直接通過new來獲取實例,Unsafe內部有一個靜態方法getUnsafe可以獲取實例;
```
private Unsafe() {}
public static Unsafe getUnsafe() {
Class cc = sun.reflect.Reflection.getCallerClass(2);
if (cc.getClassLoader() != null) //沒有被任何類加載器加載時是null。它會拋出SecurityException 異常
throw new SecurityException("Unsafe");
return theUnsafe;
}
```
getUnsafe()方法獲取實例,直接調用時會拋出異常java.lang.SecurityException: Unsafe,只有java認為是安全的代碼才可以加載并獲取Unsafe實例。如此一來,我們編寫的代碼就不能直接通過該方法獲取Unsafe實例了。
不過Unsafe的內部有一個theUnsafe變量,我們可以通過反射來獲取這個實例。獲取實例的方法很簡單,獲取theUnsafe屬性,在調用get方法即可
```
private static final Unsafe theUnsafe = new Unsafe();
// 通過反射獲取Unsafe實例
public static Unsafe getUnsafe() throws Exception{
Field f = Unsafe.class.getDeclaredField("theUnsafe"); //Internal reference
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);
return unsafe;
}
```
## 相關操作
### 1.分配內存
allocateMemory方法可以用來分配本地內存,下面模擬一個數組UnsafeArray,內部保存byte類型數據;初始化時,根據數組大小分配內存,返回address為數組的基地址,set和get時根據偏移量調用putByte和getByte修改數組內數據。
```
int BYTESIZE = 1;
public UnsafeArray(long size) {
address = UNSAFE.allocateMemory(size *BYTESIZE);
}
public byte get(long index) {
return UNSAFE.getByte(address + (index * BYTESIZE));
}
public void set(long index,byte result) {
UNSAFE.putByte(address + (index * BYTESIZE), result);
}
```
### 2.對象屬性
objectFieldOffset方法可以類的某個屬性在Class中的偏移,根據偏移量調用getInt()和UNFASE.getAndSetInt()可以獲取/設置對象實例該屬性的值。下面以SampleClass類為例,內部有一個整型i默認為5,long型默認為10
```
public final class SampleClass{
private int i = 5;
private long l = 10;
private byte[] buf = new byte[4];
}
```
下面是具體的使用過程
```
public static void testObject() throws NoSuchFieldException, SecurityException {
// 由于對象頭是8+8 int的offset應該是16
long offseti = UNFASE.objectFieldOffset(SampleClass.class.getDeclaredField("i"));
System.out.println("offset of i is " + offseti);//offset of i is 12
long offsetl = UNFASE.objectFieldOffset(SampleClass.class.getDeclaredField("l"));
System.out.println("offset of l is " + offsetl);//offset of l is 16
SampleClass sampleClass = new SampleClass();
System.out.println("testObject offset of offseti : "+ UNFASE.getInt(sampleClass, offseti));// testObject offset of offseti : 5
System.out.println("testObject offset of offsetl : "+ UNFASE.getInt(sampleClass, offsetl));// testObject offset of offsetl : 10
//
UNFASE.getAndSetInt(sampleClass, offseti, 99);
System.out.println("sampleClass.getI() = " + sampleClass.getI());// sampleClass.getI() = 99
UNFASE.getAndSetLong(sampleClass, offsetl, 88);
System.out.println("sampleClass.getL() = " + sampleClass.getL());// sampleClass.getL() = 88
}
```
## 3.CAS
CAS是java并發包中最常見的無鎖方法,底層均由unsafe實現,有如下方法:
```
/**
* 如果對象offset的當前值是expected則更新為x
*/
public final native boolean compareAndSwapObject(Object o, long offset,
Object expected,
Object x);
public final native boolean compareAndSwapInt(Object o, long offset,
int expected,
int x);
public final native boolean compareAndSwapLong(Object o, long offset,
long expected,
long x);
```
在AtomicInteger的源碼中,可以看到cas的使用,其內部使用value記錄了實際的int值,初始化時獲取了value在AtomicInteger的偏移valueOffset,在AtomicInteger提供的方法中,會使用unsafe的相關方法完成cas操作。
```
//AtomicInteger類源碼
public final int getAndSet(int newValue) {
return unsafe.getAndSetInt(this, valueOffset, newValue);
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
public final boolean weakCompareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
```
## 4.park
unsafe有兩個方法park()和unpark()可以用來停止線程和恢復線程,并發包中的很多鎖機制都使用了該方法,下面的例子中會啟動一個線程,休眠5秒或被喚醒后結束;main線程休眠2秒后unpark恢復了內部線程。
```
Thread thread = new Thread(new Runnable() {
public void run() {
UNFASE.park(false, TimeUnit.SECONDS.toNanos(5));
System.out.println("innrt thread end");
}
});
thread.start();
System.out.println("begin thread!");
TimeUnit.SECONDS.sleep(2);
UNFASE.unpark(thread);
System.out.println("main thread unpark!");
```
打印內容為:
```
0s-begin thread!
2s-main thread unpark!
2s-innrt thread end
```
## 5.monitor
臨界區相關的方法有
```
public native void monitorEnter(Object o);
public native void monitorExit(Object o);
public native boolean tryMonitorEnter(Object o);
```
使用方法類似與synchronized類似,進入臨界區之前需要獲得鎖,跳出臨界區后要釋放鎖。park中的例子可以改寫為下面的方式:
```
final Object lock = new Object();
Thread thread = new Thread(new Runnable() {
public void run() {
UNFASE.monitorEnter(lock);
System.out.println("innrt thread end");
}
});
thread.start();
UNFASE.monitorEnter(lock);
System.out.println("begin thread!");
TimeUnit.SECONDS.sleep(2);
UNFASE.monitorExit(lock);
System.out.println("main thread unpark!");
```
# netty對unsafe的使用
netty中在內存相關的操作和cas中使用了unsafe對象,具體程序在PlatformDependent0中。
1.獲取DirectByteBuffer的地址
由于unsafe主要用于分配直接內存,首先獲取了nio的Buffer中是否有address屬性,如果內有則不需要獲取Unsafe對象實例了。
```
inal ByteBuffer direct = ByteBuffer.allocateDirect(1);
......
final Field field = Buffer.class.getDeclaredField("address");
field.setAccessible(true);
// if direct really is a direct buffer, address will be non-zero
if (field.getLong(direct) == 0) {
return null;
}
return field;
```
2. 獲取Unsafe實例,使用反射的方法獲取theUnsafe屬性
3. 大量使用了內存相關的方法