Unsafe是位于sun.misc包下的一個類,主要提供一些用于執行低級别、不安全操作的方法,如直接通路系統記憶體資源、自主管理記憶體資源等,這些方法在提升Java運作效率、增強Java語言底層資源操作能力方面起到了很大的作用。
但是,這個類的作者不希望我們使用它,因為我們雖然我們擷取到了對底層的控制權,但是也增大了風險,安全性正是Java相對于C++/C的優勢。因為該類在sun.misc包下,預設是被BootstrapClassLoader加載的。如果我們在程式中去調用這個類的話,我們使用的類加載器肯定是 AppClassLoader,問題是在Unsafe中是這樣寫的:
private static final Unsafe theUnsafe;
private Unsafe() {
}
@CallerSensitive
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
将構造函數私有,然後提供了一個靜态方法去擷取目前類執行個體。在getUnsafe()方法中首先判斷目前類加載器是否為空,因為使用 BootstrapClassLoader 本身就是空,它是用c++實作的,這樣就限制了我們在自己的代碼中使用這個類。
但是同時作者也算是給我們提供了一個後門,因為Java有反射機制。調用的思路就是将theUnsafe對象設定為可見。
Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) theUnsafeField.get(null);
System.out.println(unsafe);
unsafe類功能介紹:

記憶體操作
這部分主要包含堆外記憶體的配置設定、拷貝、釋放、給定位址值操作等方法。
//配置設定記憶體, 相當于C++的malloc函數
public native long allocateMemory(long bytes);
//擴充記憶體
public native long reallocateMemory(long address, long bytes);
//釋放記憶體
public native void freeMemory(long address);
//在給定的記憶體塊中設定值
public native void setMemory(Object o, long offset, long bytes, byte value);
//記憶體拷貝
public native void copyMemory(Object srcBase, long srcOffset, Object destBase, long destOffset, long bytes);
//擷取給定位址值,忽略修飾限定符的通路限制。與此類似操作還有: getInt,getDouble,getLong,getChar等
public native Object getObject(Object o, long offset);
//為給定位址設定值,忽略修飾限定符的通路限制,與此類似操作還有: putInt,putDouble,putLong,putChar等
public native void putObject(Object o, long offset, Object x);
//擷取給定位址的byte類型的值(當且僅當該記憶體位址為allocateMemory配置設定時,此方法結果為确定的)
public native byte getByte(long address);
//為給定位址設定byte類型的值(當且僅當該記憶體位址為allocateMemory配置設定時,此方法結果才是确定的)
public native void putByte(long address, byte x);
通常,我們在Java中建立的對象都處于堆内記憶體(heap)中,堆内記憶體是由JVM所管控的Java程序記憶體,并且它們遵循JVM的記憶體管理機制,JVM會采用垃圾回收機制統一管理堆記憶體。與之相對的是堆外記憶體,存在于JVM管控之外的記憶體區域,Java中對堆外記憶體的操作,依賴于Unsafe提供的操作堆外記憶體的native方法。
使用堆外記憶體的原因
對垃圾回收停頓的改善。由于堆外記憶體是直接受作業系統管理而不是JVM,是以當我們使用堆外記憶體時,即可保持較小的堆内記憶體規模。進而在GC時減少回收停頓對于應用的影響。
提升程式I/O操作的性能。通常在I/O通信過程中,會存在堆内記憶體到堆外記憶體的資料拷貝操作,對于需要頻繁進行記憶體間資料拷貝且生命周期較短的暫存資料,都建議存儲到堆外記憶體。
典型應用
DirectByteBuffer是Java用于實作堆外記憶體的一個重要類,通常用在通信過程中做緩沖池,如在Netty、MINA等NIO架構中應用廣泛。DirectByteBuffer對于堆外記憶體的建立、使用、銷毀等邏輯均由Unsafe提供的堆外記憶體API來實作。
下面的代碼為DirectByteBuffer構造函數,建立DirectByteBuffer的時候,通過Unsafe.allocateMemory配置設定記憶體、Unsafe.setMemory進行記憶體初始化,而後建構Cleaner對象用于跟蹤DirectByteBuffer對象的垃圾回收,以實作當DirectByteBuffer被垃圾回收時,配置設定的堆外記憶體一起被釋放。
DirectByteBuffer(int cap) { // package-private
super(-1, 0, cap, cap);
boolean pa = VM.isDirectMemoryPageAligned();
int ps = Bits.pageSize();
long size = Math.max(1L, (long)cap + (pa ? ps : 0));
Bits.reserveMemory(size, cap);
long base = 0;
try {
//配置設定記憶體,傳回基位址
base = unsafe.allocateMemory(size);
} catch (OutOfMemoryError x) {
Bits.unreserveMemory(size, cap);
throw x;
}
//記憶體初始化
unsafe.setMemory(base, size, (byte) 0);
if (pa && (base % ps != 0)) {
// Round up to page boundary
address = base + ps - (base & (ps - 1));
} else {
address = base;
}
//跟蹤directbytebuffer 對象的垃圾回收,實作堆外記憶體的釋放
cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
att = null;
}
上面最後一句代碼通過
Cleaner.create()
來進行對象監控,釋放堆外記憶體。這裡是如何做到的呢?跟蹤一下Cleaner類:
public class Cleaner extends PhantomReference<Object> {
public static Cleaner create(Object var0, Runnable var1) {
return var1 == null ? null : add(new Cleaner(var0, var1));
}
}
可以看到繼承了PhantomReference,Java中的4大引用類型我們都知道。PhantomReference的作用于其他的Refenrence作用大有不同。像 SoftReference、WeakReference都是為了保證引用的類對象能在不用的時候及時的被回收,但是 PhantomReference 并不會決定對象的生命周期。如果一個對象僅持有虛引用,那麼它就和沒有任何引用一樣,對象不可達時就會被垃圾回收器回收,但是任何時候都無法通過虛引用獲得對象。虛引用主要用來跟蹤對象被垃圾回收器回收的活動。
那他的作用到底是啥呢?準确來說 PhantomReference 給使用者提供了一種機制-來監控對象的垃圾回收的活動。
可能這樣說不是太明白,我來舉個例子:
package com.rickiyang.learn.javaagent;
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.reflect.Field;
/**
* @author rickiyang
* @date 2019-08-08
* @Desc
*/
public class TestPhantomReference {
public static boolean isRun = true;
public static void main(String[] args) throws Exception {
String str = new String("123");
System.out.println(str.getClass() + "@" + str.hashCode());
final ReferenceQueue<String> referenceQueue = new ReferenceQueue<>();
new Thread(() -> {
while (isRun) {
Object obj = referenceQueue.poll();
if (obj != null) {
try {
Field rereferent = Reference.class.getDeclaredField("referent");
rereferent.setAccessible(true);
Object result = rereferent.get(obj);
System.out.println("gc will collect:"
+ result.getClass() + "@"
+ result.hashCode() + "\t"
+ result);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}).start();
PhantomReference<String> weakRef = new PhantomReference<>(str, referenceQueue);
str = null;
Thread.currentThread().sleep(2000);
System.gc();
Thread.currentThread().sleep(2000);
isRun = false;
}
}
上面這段代碼的含義是new PhantomReference(),因為PhantomReference必須的維護一個ReferenceQueue用來儲存目前被虛引用的對象。上例中手動去調用referenceQueue.poll()方法,這裡你需要注意的是并不是我們主動去釋放queue中的對象,你跟蹤進去 poll() 方法可以看到有一個全局鎖對象,隻有當目前對象失去了引用之後才會釋放鎖,poll()方法才能執行。在執行poll()方法釋放對象的時候我們可以針對這個對象做一些監控。這就是 PhantomReference 的意義所在。
說回到 Cleaner, 通過看源碼,create()方法調用了add()方法,在Cleaner類裡面維護了一個雙向連結清單,将每一個add進來的Cleaner對象都添加到這個連結清單中維護。那麼在Cleaner 連結清單中的對象實在何時被釋放掉呢?
注意到 Cleaner中有一個clean()方法:
public void clean() {
if (remove(this)) {
try {
this.thunk.run();
} catch (final Throwable var2) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
if (System.err != null) {
(new Error("Cleaner terminated abnormally", var2)).printStackTrace();
}
System.exit(1);
return null;
}
});
}
}
}
remove()方法是将該對象從内部維護的雙向連結清單中清除。下面緊跟着是thunk.run() ,thunk = 我們通過create()方法傳進來的參數,在``DirectByteBuffer中那就是:Cleaner.create(this, new Deallocator(base, size, cap))`,Deallocator類也是一個線程:
private static class Deallocator
implements Runnable
{
private static Unsafe unsafe = Unsafe.getUnsafe();
//省略無關 代碼
public void run() {
if (address == 0) {
// Paranoia
return;
}
unsafe.freeMemory(address);
address = 0;
Bits.unreserveMemory(size, capacity);
}
}
看到在run方法中調用了freeMemory()去釋放掉對象。
在 Reference類中調用了該方法,Reference 類中的靜态代碼塊 有個一内部類:ReferenceHandler,它繼承了 Thread,在run方法中調用了 tryHandlePending(),并且被設定為守護線程,意味着會循環不斷的處理pending連結清單中的對象引用。
這裡要注意的點是:
Cleaner本身不帶有清理邏輯,所有的邏輯都封裝在thunk中,是以thunk是怎麼實作的才是最關鍵的。
另外,Java 最新核心技術系列教程和示例源碼看這裡:
https://github.com/javastacks/javastackstatic {
ThreadGroup tg = Thread.currentThread().getThreadGroup();
for (ThreadGroup tgn = tg;
tgn != null;
tg = tgn, tgn = tg.getParent());
Thread handler = new ReferenceHandler(tg, "Reference Handler");
/* If there were a special system-only priority greater than
* MAX_PRIORITY, it would be used here
*/
handler.setPriority(Thread.MAX_PRIORITY);
handler.setDaemon(true);
handler.start();
// provide access in SharedSecrets
SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
@Override
public boolean tryHandlePendingReference() {
return tryHandlePending(false);
}
});
}
static boolean tryHandlePending(boolean waitForNotify) {
Reference<Object> r;
Cleaner c;
try {
synchronized (lock) {
if (pending != null) {
r = pending;
//如果目前Reference對象是Cleaner類型的就進行特殊處理
c = r instanceof Cleaner ? (Cleaner) r : null;
// unlink 'r' from 'pending' chain
pending = r.discovered;
r.discovered = null;
} else {
// The waiting on the lock may cause an OutOfMemoryError
// because it may try to allocate exception objects.
if (waitForNotify) {
lock.wait();
}
// retry if waited
return waitForNotify;
}
}
} catch (OutOfMemoryError x) {
Thread.yield();
// retry
return true;
} catch (InterruptedException x) {
// retry
return true;
}
// clean 不為空的時候,走清理的邏輯
if (c != null) {
c.clean();
return true;
}
ReferenceQueue<? super Object> q = r.queue;
if (q != ReferenceQueue.NULL) q.enqueue(r);
return true;
}
tryHandlePending這段代碼的意思是:
如果一個對象經過JVM檢測他已經沒有強引用了,但是還有 弱引用 或者 軟引用 或者 虛引用的情況下,那麼就會把此對象放到一個名為pending的連結清單裡,這個連結清單是通過Reference.discovered域連接配接在一起的。
ReferenceHandler這個線程會一直從連結清單中取出被pending的對象,它可能是WeakReference,也可能是SoftReference,當然也可能是PhantomReference和Cleaner。如果是Cleaner,那就直接調用Cleaner的clean方法,然後就結束了。其他的情況下,要交給這個對象所關聯的queue,以便于後續的處理。
關于堆外記憶體配置設定和回收的代碼我們就先分析到這裡。需要注意的是對外記憶體回收的時機也是不确定的,是以不要持續配置設定一些大對象到堆外,如果沒有被回收掉,這是一件很可怕的事情。畢竟它無法被JVM檢測到。
記憶體屏障
硬體層的記憶體屏障分為兩種:Load Barrier 和 Store Barrier即讀屏障和寫屏障。記憶體屏障有兩個作用:阻止屏障兩側的指令重排序;強制把寫緩沖區/高速緩存中的髒資料等寫回主記憶體,讓緩存中相應的資料失效。在Unsafe中提供了三個方法來操作記憶體屏障:
//讀屏障,禁止load操作重排序。屏障前的load操作不能被重排序到屏障後,屏障後的load操作不能被重排序到屏障前
public native void loadFence();
//寫屏障,禁止store操作重排序。屏障前的store操作不能被重排序到屏障後,屏障後的store操作不能被重排序到屏障前
public native void storeFence();
//全能屏障,禁止load、store操作重排序
public native void fullFence();
先簡單了解兩個指令:
- Store:将處理器緩存的資料重新整理到記憶體中。
- Load:将記憶體存儲的資料拷貝到處理器的緩存中。
JVM平台提供了一下幾種記憶體屏障:
StoreLoad Barriers同時具備其他三個屏障的效果,是以也稱之為全能屏障(mfence),是目前大多數處理器所支援的;但是相對其他屏障,該屏障的開銷相對昂貴。
loadFence
實作了LoadLoad Barriers,該操作禁止了指令的重排序。
storeFence
實作了 StoreStore Barriers,確定屏障前的寫操作能夠立刻刷入到主記憶體,并且確定屏障前的寫操作一定先于屏障後的寫操作。即保證了記憶體可見性和禁止指令重排序。
fullFence
實作了 StoreLoad Barriers,強制所有在mfence指令之前的store/load指令,都在該mfence指令執行之前被執行;所有在mfence指令之後的store/load指令,都在該mfence指令執行之後被執行。
在 JDK 中調用了 記憶體屏障這幾個方法的實作類有 StampedLock。關于StampedLock的實作我們後面會專門抽出一篇去講解。它并沒有去實作AQS隊列。而是采用了 其他方式實作。
系統相關
這部分包含兩個擷取系統相關資訊的方法。
// 終止挂起的線程,恢複正常.java.util.concurrent包中挂起操作都是在LockSupport類實作的,其底層正是使用這兩個方法
public native void unpark(Object thread);
// 線程調用該方法,線程将一直阻塞直到逾時,或者是中斷條件出現。
public native void park(boolean isAbsolute, long time);
//獲得對象鎖(可重入鎖)
@Deprecated
public native void monitorEnter(Object o);
//釋放對象鎖
@Deprecated
public native void monitorExit(Object o);
//嘗試擷取對象鎖
@Deprecated
public native boolean tryMonitorEnter(Object o);
将一個線程進行挂起是通過 park 方法實作的,調用park()後,線程将一直 阻塞 直到 逾時 或者 中斷 等條件出現。unpark可以釋放一個被挂起的線程,使其恢複正常。整個并發架構中對線程的挂起操作被封裝在LockSupport類中,LockSupport 類中有各種版本 pack 方法,但最終都調用了Unsafe.park()方法。 我們來看一個例子:
package leetcode;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
import java.util.concurrent.TimeUnit;
/**
* @author: rickiyang
* @date: 2019/8/10
* @description:
*/
public class TestUsafe {
private static Thread mainThread;
public Unsafe getUnsafe() throws Exception {
Field theUnsafeField = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafeField.setAccessible(true);
return (Unsafe) theUnsafeField.get(null);
}
public void testPark() throws Exception {
Unsafe unsafe = getUnsafe();
mainThread = Thread.currentThread();
System.out.println(String.format("park %s", mainThread.getName()));
unsafe.park(false, TimeUnit.SECONDS.toNanos(3));
new Thread(() -> {
System.out.println(String.format("%s unpark %s", Thread.currentThread().getName(),
mainThread.getName()));
unsafe.unpark(mainThread);
}).start();
System.out.println("main thread is done");
}
public static void main(String[] args) throws Exception {
TestUsafe testUsafe = new TestUsafe();
testUsafe.testPark();
}
}
運作上面的例子,那你會發現在第29行
park
方法設定了逾時時間為3秒後,會阻塞目前主線程,直到逾時時間到達,下面的代碼才會繼續執行。
對象操作
Unsafe類中提供了多個方法來進行 對象執行個體化 和 擷取對象的偏移位址 的操作:
// 傳入一個Class對象并建立該執行個體對象,但不會調用構造方法
public native Object allocateInstance(Class<?> cls) throws InstantiationException;
// 擷取字段f在執行個體對象中的偏移量
public native long objectFieldOffset(Field f);
// 傳回值就是f.getDeclaringClass()
public native Object staticFieldBase(Field f);
// 靜态屬性的偏移量,用于在對應的Class對象中讀寫靜态屬性
public native long staticFieldOffset(Field f);
// 獲得給定對象偏移量上的int值,所謂的偏移量可以簡單了解為指針指向該變量;的記憶體位址,
// 通過偏移量便可得到該對象的變量,進行各種操作
public native int getInt(Object o, long offset);
// 設定給定對象上偏移量的int值
public native void putInt(Object o, long offset, int x);
// 獲得給定對象偏移量上的引用類型的值
public native Object getObject(Object o, long offset);
// 設定給定對象偏移量上的引用類型的值
public native void putObject(Object o, long offset, Object x););
// 設定給定對象的int值,使用volatile語義,即設定後立馬更新到記憶體對其他線程可見
public native void putIntVolatile(Object o, long offset, int x);
// 獲得給定對象的指定偏移量offset的int值,使用volatile語義,總能擷取到最新的int值。
public native int getIntVolatile(Object o, long offset);
// 與putIntVolatile一樣,但要求被操作字段必須有volatile修飾
public native void putOrderedInt(Object o, long offset, int x);
allocateInstance方法在這幾個場景下很有用:跳過對象的執行個體化階段(通過構造函數)、忽略構造函數的安全檢查(反射newInstance()時)、你需要某類的執行個體但該類沒有public的構造函數。
舉個例子:
public class User {
private String name;
private int age;
private static String address = "beijing";
public User(){
name = "xiaoming";
}
public String getname(){
return name;
}
}
/**
* 執行個體化對象
* @throws Exception
*/
public void newInstance() throws Exception{
TestUsafe testUsafe = new TestUsafe();
Unsafe unsafe = testUsafe.getUnsafe();
User user = new User();
System.out.println(user.getname());
User user1 = User.class.newInstance();
System.out.println(user1.getname());
User o = (User)unsafe.allocateInstance(User.class);
System.out.println(o.getname());
}
列印的結果可以看到最後輸出的是null,說明構造函數未被加載。可以進一步實驗,将User類中的構造函數設定為 private,你會發現在前面兩種執行個體化方式檢查期就報錯。但是第三種是可以用的。這是因為allocateInstance隻是給對象配置設定了記憶體,它并不會初始化對象中的屬性。
下面是對象操作的使用示例:
public void testObject() throws Exception{
TestUsafe testUsafe = new TestUsafe();
Unsafe unsafe = testUsafe.getUnsafe();
//通過allocateInstance建立對象,為其配置設定記憶體位址,不會加載構造函數
User user = (User) unsafe.allocateInstance(User.class);
System.out.println(user);
// Class && Field
Class<? extends User> userClass = user.getClass();
Field name = userClass.getDeclaredField("name");
Field age = userClass.getDeclaredField("age");
Field location = userClass.getDeclaredField("address");
// 擷取執行個體域name和age在對象記憶體中的偏移量并設定值
System.out.println(unsafe.objectFieldOffset(name));
unsafe.putObject(user, unsafe.objectFieldOffset(name), "xiaoming");
System.out.println(unsafe.objectFieldOffset(age));
unsafe.putInt(user, unsafe.objectFieldOffset(age), 18);
System.out.println(user);
// 擷取定義location字段的類
Object staticFieldBase = unsafe.staticFieldBase(location);
System.out.println(staticFieldBase);
// 擷取static變量address的偏移量
long staticFieldOffset = unsafe.staticFieldOffset(location);
// 擷取static變量address的值
System.out.println(unsafe.getObject(staticFieldBase, staticFieldOffset));
// 設定static變量address的值
unsafe.putObject(staticFieldBase, staticFieldOffset, "tianjin");
System.out.println(user + " " + user.getAddress());
}
對象執行個體布局與記憶體大小
一個Java對象占用多大的記憶體空間呢?這個問題很值得讀者朋友去查一下。 因為這個輸出本篇的重點是以簡單說一下。一個 Java 對象在記憶體中由對象頭、示例資料和對齊填充構成。對象頭存儲了對象運作時的基本資料,如 hashCode、鎖狀态、GC 分代年齡、類型指針等等。執行個體資料是對象中的非靜态字段值,可能是一個原始類型的值,也可能是一個指向其他對象的指針。對齊填充就是 padding,保證對象都采用 8 位元組對齊。除此以外,在 64 位虛拟機中還可能會開啟指針壓縮,将 8 位元組的指針壓縮為 4 位元組,這裡就不再過多介紹了。
也就是說一個 Java 對象在記憶體中,首先是對象頭,然後是各個類中字段的排列,這之間可能會有 padding 填充。這樣我們大概就能了解字段偏移量的含義了,它實際就是每個字段在記憶體中所處的位置。
public class User {
private String name;
private int age;
}
TestUsafe testUsafe = new TestUsafe();
Unsafe unsafe = testUsafe.getUnsafe();
for (Field field : User.class.getDeclaredFields()) {
System.out.println(field.getName() + "-" + field.getType() + ": " + unsafe.objectFieldOffset(field));
}
結果:
name-class java.lang.String: 16
age-int: 12
從上面的運作結果中可以: age:偏移值為12,即前面 12 個位元組的對象頭;
name:name從16位元組開始,因為int 類型的age占了4個位元組。
繼續算下去整個對象占用的空間,對象頭12,age 4,name 是指針類型,開啟指針壓縮占用4個位元組,那麼User對象整個占用20位元組,因為上面說的padding填充,必須8位元組對齊,那麼實際上會補上4個位元組的填充,即一共占用了24個位元組。
按照這種計算方式,我們可以位元組寫一個計算size的工具類:
public static long sizeOf(Object o) throws Exception{
TestUsafe testUsafe = new TestUsafe();
Unsafe unsafe = testUsafe.getUnsafe();
HashSet<Field> fields = new HashSet<Field>();
Class c = o.getClass();
while (c != Object.class) {
for (Field f : c.getDeclaredFields()) {
if ((f.getModifiers() & Modifier.STATIC) == 0) {
fields.add(f);
}
}
//如果有繼承父類的話,父類中的屬性也是要計算的
c = c.getSuperclass();
}
//計算每個字段的偏移量,因為第一個字段的偏移量即在對象頭的基礎上偏移的
//是以隻需要比較目前偏移量最大的字段即表示這是該對象最後一個字段的位置
long maxSize = 0;
for (Field f : fields) {
long offset = unsafe.objectFieldOffset(f);
if (offset > maxSize) {
maxSize = offset;
}
}
//上面計算的是對象最後一個字段的偏移量起始位置,java中對象最大長度是8個位元組(long)
//這裡的計算方式是 将 目前偏移量 / 8 + 8位元組 的padding
return ((maxSize/8) + 1) * 8;
}
上面的工具類計算的結果也是24。
class相關操作
//靜态屬性的偏移量,用于在對應的Class對象中讀寫靜态屬性
public native long staticFieldOffset(Field f);
//擷取一個靜态字段的對象指針
public native Object staticFieldBase(Field f);
//判斷是否需要初始化一個類,通常在擷取一個類的靜态屬性的時候(因為一個類如果沒初始化,它的靜态屬性也不會初始化)使用。 當且僅當ensureClassInitialized方法不生效時傳回false
public native boolean shouldBeInitialized(Class<?> c);
//確定類被初始化
public native void ensureClassInitialized(Class<?> c);
//定義一個類,可用于動态建立類,此方法會跳過JVM的所有安全檢查,預設情況下,ClassLoader(類加載器)和ProtectionDomain(保護域)執行個體來源于調用者
public native Class<?> defineClass(String name, byte[] b, int off, int len,
ClassLoader loader,
ProtectionDomain protectionDomain);
//定義一個匿名類,可用于動态建立類
public native Class<?> defineAnonymousClass(Class<?> hostClass, byte[] data, Object[] cpPatches);
數組操作
數組操作主要有兩個方法:
//傳回數組中第一個元素的偏移位址
public native int arrayBaseOffset(Class<?> arrayClass);
//傳回數組中一個元素占用的大小
public native int arrayIndexScale(Class<?> arrayClass);
CAS操作
相信所有的開發者對這個詞都不陌生,在AQS類中使用了無鎖的方式來進行并發控制,主要就是CAS的功勞。
CAS的全稱是Compare And Swap 即比較交換,其算法核心思想如下
執行函數:CAS(V,E,N)
包含3個參數
V表示要更新的變量
E表示預期值
N表示新值
如果V值等于E值,則将V的值設為N。若V值和E值不同,則說明已經有其他線程做了更新,則目前線程什麼都不做。通俗的了解就是CAS操作需要我們提供一個期望值,當期望值與目前線程的變量值相同時,說明沒有别的線程修改該值,目前線程可以進行修改,也就是執行CAS操作,但如果期望值與目前線程不符,則說明該值已被其他線程修改,此時不執行更新操作,但可以選擇重新讀取該變量再嘗試再次修改該變量,也可以放棄操作。
Unsafe類中提供了三個方法來進行CAS操作:
public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object update);
public final native boolean compareAndSwapInt(Object o, long offset, int expected,int update);
public final native boolean compareAndSwapLong(Object o, long offset, long expected, long update);
另外,在 JDK1.8中新增了幾個 CAS 的方法,他們的實作是基于上面三個方法做的一層封裝:
//1.8新增,給定對象o,根據擷取記憶體偏移量指向的字段,将其增加delta,
//這是一個CAS操作過程,直到設定成功方能退出循環,傳回舊值
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
//擷取記憶體中最新值
v = getIntVolatile(o, offset);
//通過CAS操作
} while (!compareAndSwapInt(o, offset, v, v + delta));
return v;
}
//1.8新增,方法作用同上,隻不過這裡操作的long類型資料
public final long getAndAddLong(Object o, long offset, long delta) {
long v;
do {
v = getLongVolatile(o, offset);
} while (!compareAndSwapLong(o, offset, v, v + delta));
return v;
}
//1.8新增,給定對象o,根據擷取記憶體偏移量對于字段,将其 設定為新值newValue,
//這是一個CAS操作過程,直到設定成功方能退出循環,傳回舊值
public final int getAndSetInt(Object o, long offset, int newValue) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!compareAndSwapInt(o, offset, v, newValue));
return v;
}
// 1.8新增,同上,操作的是long類型
public final long getAndSetLong(Object o, long offset, long newValue) {
long v;
do {
v = getLongVolatile(o, offset);
} while (!compareAndSwapLong(o, offset, v, newValue));
return v;
}
//1.8新增,同上,操作的是引用類型資料
public final Object getAndSetObject(Object o, long offset, Object newValue) {
Object v;
do {
v = getObjectVolatile(o, offset);
} while (!compareAndSwapObject(o, offset, v, newValue));
return v;
}
CAS在java.util.concurrent.atomic相關類、Java AQS、CurrentHashMap等實作上有非常廣泛的應用。