### sun.misc.Unsafe
Java最初被設計為一種安全的受控環境。盡管如此,HotSpot還是包含了一個后門sun.misc.Unsafe,提供了一些可以直接操控內存和線程的底層操作。Unsafe被JDK廣泛應用于java.nio和并發包等實現中,這個不安全的類提供了一個觀察HotSpot JVM內部結構并且可以對其進行修改,但是不建議在生產環境中使用
作用:可以用來在任意內存地址位置處讀寫數據,支持一些CAS原子操作
### 獲取Unsafe實例
Unsafe對象不能直接通過new Unsafe\(\)或調用Unsafe.getUnsafe\(\)獲取,原因如下\(訪問限制\):
* 不能直接new Unsafe\(\),原因是Unsafe被設計成單例模式,構造方法是私有的;
* 不能通過調用Unsafe.getUnsafe\(\)獲取,因為getUnsafe被設計成只能從引導類加載器\(bootstrap class loader\)加載,否則拋出SecurityException異常
```
@CallerSensitive
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
```
獲取實例
```
//方法一:我們可以令我們的代碼“受信任”。運行程序時,使用bootclasspath選項,指定系統類路徑加上你使用的一個Unsafe路徑
java -Xbootclasspath:/usr/jdk1.7.0/jre/lib/rt.jar:. com.mishadoff.magic.UnsafeClient
// 方法二
static {
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
UNSAFE = (Unsafe) field.get(null);
} catch (Exception e) {
}
}
```
注意:忽略你的IDE。比如:eclipse顯示”Access restriction…”錯誤,但如果你運行代碼,它將正常運行。如果這個錯誤提示令人煩惱,可以通過以下設置來避免:
Preferences -> Java -> Compiler -> Errors/Warnings -> Deprecated and restricted API -> Forbidden reference -> Warning
### 重點API
* **allocateInstance\(Class<?> var1\)不調用構造方法生成對象**
```
User instance = (User) UNSAFE.allocateInstance(User.class);
```
* **objectFieldOffset\(Field var1\)返回成員屬性在內存中的地址相對于對象內存地址的偏移量**
* **putLong,putInt,putDouble,putChar,putObject等方法,直接修改內存數據(可以越過訪問權限)**
```
package com.quancheng;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
public class CollectionApp {
private static sun.misc.Unsafe UNSAFE;
public static void main(String[] args) {
try {
User instance = (User) UNSAFE.allocateInstance(User.class);
instance.setName("luoyoub");
System.err.println("instance:" + instance);
instance.test();
Field name = instance.getClass().getDeclaredField("name");
UNSAFE.putObject(instance, UNSAFE.objectFieldOffset(name), "huanghui");
instance.test();
} catch (Exception e) {
e.printStackTrace();
}
}
static {
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
UNSAFE = (Unsafe) field.get(null);
} catch (Exception e) {
}
}
}
class User {
private String name;
public void setName(String name) {
this.name = name;
}
public void test() {
System.err.println("hello,world" + name);
}
}
```
* copyMemory:內存數據拷貝
* freeMemory:用于釋放allocateMemory和reallocateMemory申請的內存
* compareAndSwapInt/compareAndSwapLongCAS操作
* getLongVolatile/putLongVolatile
### 使用場景
* **避免初始化**
當你想要跳過對象初始化階段,或繞過構造器的安全檢查,或實例化一個沒有任何公共構造器的類,allocateInstance方法是非常有用的,使用構造器、反射和unsafe初始化它,將得到不同的結果
```
public class CollectionApp {
private static sun.misc.Unsafe UNSAFE;
public static void main(String[] args) throws IllegalAccessException, InstantiationException {
A a = new A();
a.test(); // output ==> 1
A a1 = A.class.newInstance();
a1.test(); // output ==> 1
A instance = (A) UNSAFE.allocateInstance(A.class);
instance.test(); // output ==> 0
}
static {
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
UNSAFE = (Unsafe) field.get(null);
} catch (Exception e) {
}
}
}
class A{
private long a;
public A(){
a = 1;
}
public void test(){
System.err.println("a==>" + a);
}
}
```
* **內存崩潰\(Memory corruption\)**
Unsafe可用于繞過安全的常用技術,直接修改內存變量;實際上,反射可以實現相同的功能。但值得關注的是,我們可以修改任何對象,甚至沒有這些對象的引用
```
Guard guard = new Guard();
guard.giveAccess(); // false, no access
// bypass
Unsafe unsafe = getUnsafe();
Field f = guard.getClass().getDeclaredField("ACCESS_ALLOWED");
unsafe.putInt(guard, unsafe.objectFieldOffset(f), 42); // memory corruption
guard.giveAccess(); // true, access granted
```
注意:我們不必持有這個對象的引用
* **淺拷貝\(Shallow copy\)**
* **動態類\(Dynamic classes\)**
我們可以在運行時創建一個類,比如從已編譯的.class文件中。將類內容讀取為字節數組,并正確地傳遞給defineClass方法;當你必須動態創建類,而現有代碼中有一些代理, 這是很有用的
```
private static byte[] getClassContent() throws Exception {
File f = new File("/home/mishadoff/tmp/A.class");
FileInputStream input = new FileInputStream(f);
byte[] content = new byte[(int)f.length()];
input.read(content);
input.close();
return content;
}
byte[] classContents = getClassContent();
Class c = getUnsafe().defineClass(
null, classContents, 0, classContents.length);
c.getMethod("a").invoke(c.newInstance(), null); // 1
```
* **拋出異常\(Throw an Exception\)**
該方法拋出受檢異常,但你的代碼不必捕捉或重新拋出它,正如運行時異常一樣
```
getUnsafe().throwException(new IOException());
```
* **大數組\(Big Arrays\)**
正如你所知,Java數組大小的最大值為Integer.MAX\_VALUE。使用直接內存分配,我們創建的數組大小受限于堆大小;實際上,這是堆外內存(off-heap memory)技術,在java.nio包中部分可用;
這種方式的內存分配不在堆上,且不受GC管理,所以必須小心Unsafe.freeMemory\(\)的使用。它也不執行任何邊界檢查,所以任何非法訪問可能會導致JVM崩潰
```
class SuperArray {
private final static int BYTE = 1;
private long size;
private long address;
public SuperArray(long size) {
this.size = size;
address = getUnsafe().allocateMemory(size * BYTE);
}
public void set(long i, byte value) {
getUnsafe().putByte(address + i * BYTE, value);
}
public int get(long idx) {
return getUnsafe().getByte(address + idx * BYTE);
}
public long size() {
return size;
}
}
long SUPER_SIZE = (long)Integer.MAX_VALUE * 2;
SuperArray array = new SuperArray(SUPER_SIZE);
System.out.println("Array size:" + array.size()); // 4294967294
for (int i = 0; i < 100; i++) {
array.set((long)Integer.MAX_VALUE + i, (byte)3);
sum += array.get((long)Integer.MAX_VALUE + i);
}
System.out.println("Sum of 100 elements:" + sum); // 300
```
* **并發\(Concurrency\)**
幾句關于Unsafe的并發性。compareAndSwap方法是原子的,并且可用來實現高性能的、無鎖的數據結構
* **掛起與恢復**
定義:
```
public native void unpark(Thread jthread);
public native void park(boolean isAbsolute, long time); // isAbsolute參數是指明時間是絕對的,還是相對的
```
將一個線程進行掛起是通過park方法實現的,調用park后,線程將一直阻塞直到超時或者中斷等條件出現。unpark可以終止一個掛起的線程,使其恢復正常。整個并發框架中對線程的掛起操作被封裝在 LockSupport類中,LockSupport類中有各種版本pack方法,但最終都調用了Unsafe.park\(\)方法;
unpark函數為線程提供“許可\(permit\)”,線程調用park函數則等待“許可”。這個有點像信號量,但是這個“許可”是不能疊加的,“許可”是一次性的;比如線程B連續調用了三次unpark函數,當線程A調用park函數就使用掉這個“許可”,如果線程A再次調用park,則進入等待狀態,見下例Example1
```
Example1:
// 針對當前線程已經調用過unpark(多次調用unpark的效果和調用一次unpark的效果一樣)
public static void main(String[] args) throws InterruptedException {
Thread currThread = Thread.currentThread();
UNSAFE.unpark(currThread);
UNSAFE.unpark(currThread);
UNSAFE.unpark(currThread);
UNSAFE.park(false, 0);
UNSAFE.park(false, 0);
System.out.println("SUCCESS!!!");
}
// 恢復線程interrupt() && UNSAFE.unpark()運行結果一樣
public static void main(String[] args) throws InterruptedException {
Thread currThread = Thread.currentThread();
new Thread(()->{
try {
Thread.sleep(3000);
System.err.println("sub thread end");
// currThread.interrupt();
UNSAFE.unpark(currThread);
} catch (Exception e) {
e.printStackTrace();
}
}).start();
UNSAFE.park(false, 0);
System.out.println("SUCCESS!!!");
}
// 如果是相對時間也就是isAbsolute為false(注意這里后面的單位納秒)到期的時候,與Thread.sleep效果相同,具體有什么區別有待深入研究
//相對時間后面的參數單位是納秒
UNSAFE.park(false, 3000000000l);
System.out.println("SUCCESS!!!");
long time = System.currentTimeMillis()+3000;
//絕對時間后面的參數單位是毫秒
UNSAFE.park(true, time);
System.out.println("SUCCESS!!!");
```
注意,unpark函數可以先于park調用。比如線程B調用unpark函數,給線程A發了一個“許可”,那么當線程A調用park時,它發現已經有“許可”了,那么它會馬上再繼續運行。實際上,park函數即使沒有“許可”,有時也會無理由地返回,實際上在SUN Jdk中,object.wait\(\)也有可能被假喚醒;
_注意:unpark方法最好不要在調用park前對當前線程調用unpark_
#### Unsafe API
```
sun.misc.Unsafe類包含105個方法。實際上,對各種實體操作有幾組重要方法,其中的一些如下:
Info.僅返回一些低級的內存信息
addressSize
pageSize
Objects.提供用于操作對象及其字段的方法
allocateInstance ##直接獲取對象實例
objectFieldOffset
Classes.提供用于操作類及其靜態字段的方法
staticFieldOffset
defineClass
defineAnonymousClass
ensureClassInitialized
Arrays.操作數組
arrayBaseOffset
arrayIndexScale
Synchronization.低級的同步原語
monitorEnter
tryMonitorEnter
monitorExit
compareAndSwapInt
putOrderedInt
Memory.直接內存訪問方法
allocateMemory
copyMemory
freeMemory
getAddress
getInt
putInt
```
### 知識點
* **Unsafe.park\(\)**當遇到線程終止時,會直接返回\(不同于Thread.sleep,Thread.sleep遇到thread.interrupt\(\)會拋異常\)
```
// Thread.sleep會拋異常
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()->{
try {
System.err.println("sub thread start");
Thread.sleep(10000);
System.err.println("sub thread end");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread.start();
TimeUnit.SECONDS.sleep(3);
thread.interrupt();
System.out.println("SUCCESS!!!");
}
output==>
sub thread start
SUCCESS!!!
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.quancheng.ConcurrentTest.lambda$main$0(ConcurrentTest.java:13)
at java.lang.Thread.run(Thread.java:745)
Process finished with exit code 0
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(()->{
System.err.println("sub thread start");
UNSAFE.park(false,0);
System.err.println("sub thread end");
});
thread.start();
TimeUnit.SECONDS.sleep(3);
UNSAFE.unpark(thread);
System.out.println("SUCCESS!!!");
}
output==>
sub thread start
sub thread end
SUCCESS!!!
Process finished with exit code 0
```
* unpark無法恢復處于sleep中的線程,只能與park配對使用,因為unpark發放的許可只有park能監聽到
```
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
try {
System.err.println("sub thread start");
TimeUnit.SECONDS.sleep(10);
System.err.println("sub thread end");
} catch (Exception e) {
e.printStackTrace();
}
});
thread.start();
TimeUnit.SECONDS.sleep(3);
UNSAFE.unpark(thread);
System.out.println("SUCCESS!!!");
}
```
* **park和unpark的靈活之處**
上面已經提到,unpark函數可以先于park調用,這個正是它們的靈活之處。
一個線程它有可能在別的線程unPark之前,或者之后,或者同時調用了park,那么因為park的特性,它可以不用擔心自己的park的時序問題,否則,如果park必須要在unpark之前,那么給編程帶來很大的麻煩!!
”考慮一下,兩個線程同步,要如何處理?
在Java5里是用wait/notify/notifyAll來同步的。wait/notify機制有個很蛋疼的地方是,比如線程B要用notify通知線程A,那么線程B要確保線程A已經在wait調用上等待了,否則線程A可能永遠都在等待。編程的時候就會很蛋疼。
另外,是調用notify,還是notifyAll?
notify只會喚醒一個線程,如果錯誤地有兩個線程在同一個對象上wait等待,那么又悲劇了。為了安全起見,貌似只能調用notifyAll了“
_**park/unpark模型真正解耦了線程之間的同步,線程之間不再需要一個Object或者其它變量來存儲狀態,不再需要關心對方的狀態**_
#### 參考資料
* [http://ifeve.com/sun-misc-unsafe](http://ifeve.com/sun-misc-unsafe/)
* [https://www.jianshu.com/p/a16d638bc921](https://www.jianshu.com/p/a16d638bc921)
* [https://blog.csdn.net/zhxdick/article/details/52003123](https://blog.csdn.net/zhxdick/article/details/52003123)
* [http://www.baeldung.com/java-unsafe](http://www.baeldung.com/java-unsafe)
* [https://blog.csdn.net/hengyunabc/article/details/28126139](https://blog.csdn.net/hengyunabc/article/details/28126139)
- java演變
- JDK各個版本的新特性
- JDK1.5新特性
- JDK1.6新特性
- JDK1.7新特性
- JDK1.8新特性
- JAVA基礎
- 面向對象特性
- 多態
- 方法重載
- 方法重寫
- class
- 常量
- 訪問修飾符
- 類加載路徑
- java-equals
- 局部類
- java-hashCode
- Java類初始化順序
- java-clone方法
- JAVA對象實例化的方法
- 基礎部分
- JAVA基礎特性
- JAVA關鍵字
- javabean
- static
- 日期相關
- final
- interface
- 函數式接口
- JAVA異常
- 異常屏蔽
- try-with-resource資源泄露
- JAVA引用
- WeakReference
- SoftReference
- PhantomReference
- 位運算符
- try-with-resource語法糖
- JDK冷知識
- JAVA包裝類
- JAVA基本類型與包裝類
- java.lang.Boolean
- java.lang.Integer
- java.lang.Byte
- java.lang.Short
- java.lang.Long
- java.lang.Float
- java.lang.Double
- java.lang.Character
- 日期相關
- TemporalAdjusters
- String
- 字符串常量池
- String拼接
- String編譯期優化
- StringBuilder&StringBuffer
- intern
- 注解
- java標準注解
- 內置注解
- 元注解
- 自定義注解
- 注解處理器
- JVM注解
- Java8 Annotation新特性
- 反射-Reflective
- Reflection
- Class
- Constructor
- Method
- javabean-property
- MethodHandles
- 泛型
- 類型擦除
- bridge-method
- Accessor&Mutator方法
- enum
- JAVA數組
- finalize方法
- JAR文件
- JAVA高級編程
- CORBA
- JMX
- SPI
- Java SPI使用約定
- ServiceLoader
- 實際應用
- IO
- 工具類
- JDK常用工具類
- Objects
- System
- Optional
- Throwable
- Collections
- Array
- Arrays
- System
- Unsafe
- Number
- ClassLoader
- Runtime
- Object
- Comparator
- VarHandle
- 數據結構
- 棧-Stack
- 隊列(Queue)
- Deque
- PriorityQueue
- BlockingQueue
- SynchronousQueue
- ArrayBlockingQueue
- LinkedBlockingQueue
- PriorityBlockingQueue
- ConcurrentLinkedQueue
- 列表
- 迭代器
- KV鍵值對數據類型
- HashMap
- TreeMap
- Hash沖突
- ConcurrentHashMap
- JDK1.7 ConcurrentHashMap結構
- jdk7&jdk8區別
- 集合
- Vector
- Stack
- HashSet
- TreeSet
- ArrayList
- LinkedList
- ArrayList && LinkedList相互轉換
- 線程安全的集合類
- 集合類遍歷性能
- 并發容器
- CopyOnWriteArrayList
- ConcurrentHashMap
- 同步容器
- BitMap
- BloomFilter
- SkipList
- 設計模式
- 設計模式六大原則
- 單例模式
- 代理模式
- 靜態代理
- 動態代理
- JDK動態代理
- cglib動態代理
- spring aop
- 策略模式
- SpringAOP策略模式的運用
- 生產者消費者模式
- 迭代器模式
- 函數式編程
- 方法引用
- 性能問題
- Lambda
- Lambda類型檢查
- Stream
- findFirst和findAny
- reduce
- 原始類型流特化
- 無限流
- 收集器
- 并行流
- AOP
- 靜態織入
- aspect
- aspect的定義
- AspectJ與SpringAOP
- 動態織入
- 靜態代理
- 動態代理
- JDK動態代理
- CGLib動態代理
- Spring AOP
- SpringAOP五種通知類型
- @Before
- @AfterReturning
- @AfterThrowing
- @After
- @Around
- Aspect優先級
- SpringAOP切點表達式
- within
- execution
- 嵌套調用
- 系統優化與重構
- 重疊構造器模式
- 工具類構造器優化
- 常見面試題
- new Object()到底占用幾個字節
- 訪問修飾符
- cloneable接口實現原理
- 異常分類以及處理機制
- wait和sleep的區別
- 數組在內存中如何分配
- 類加載為什么要使用雙親委派模式,有沒有什么場景是打破了這個模式
- 類的實例化順序
- 附錄
- JAVA術語
- FAQ
- 墨菲定律
- 康威定律
- 軟件設計原則
- 阿姆達爾定律
- 字節碼工具
- OSGI