您好,登錄后才能下訂單哦!
如何分析Unsafe的CAS和內(nèi)存操作的原理,相信很多沒有經(jīng)驗的人對此束手無策,為此本文總結了問題出現(xiàn)的原因和解決方法,通過這篇文章希望你能解決這個問題。
Java 語言的一大特點就是跨平臺,并且提供的有一套完美的內(nèi)存管理機制。但這都是 JVM 提供的,如果我們想要直接訪問系統(tǒng)內(nèi)存資源、自主管理內(nèi)存資源等就無法實現(xiàn)。于是 Java 又提供了一個魔法類:Unsafe。
Unsafe 類位于 sun.misc 包中。從名字看,這個類就是一個不安全的類,實際上它確實是封裝了一些不安全的操作!
Unsafe 類和 String 類一樣的被定義為 final,也就是說它不可以被繼承。并且 Unsafe 被設計成了單例,構造函數(shù)是私有的,只能通過 getUnsafe 方法獲得它。除此之外,getUnsafe 方法還設置了限制條件,只有授信的代碼才能獲得該類的實例。哪些是授信的代碼呢?當然是 JDK 庫里面的類是可以隨意使用的。
說了半天,這個類,我們無法使用,你講它又何意義?
別急,Java 雖然不建議我們使用它,但是我們還是可以通過兩種方式來使用它。
第一種方式是:讓我們的代碼在啟動時“授信”。在運行程序時,指定 bootclasspath 選項,讓你使用 Unsafe 實例的類被引導類加載器加載,從而通過 Unsafe.getUnsafe 方法安全的獲取 Unsafe 實例。
這個做法比較少用,所以推薦大家采用第二種方法:通過反射來使用它。
注意有的 IDE 可能支持的不是很友好。比如:eclipse 顯示”Access restriction…”錯誤,但如果你運行代碼,它將正常運行。如果這個錯誤提示令人煩惱,可以通過以下設置來避免:
Unsafe 有 8 大功能,很多號主只講了它的 CAS 功能。
如上圖所示,Unsafe 提供的 105 個 API 大致可分為內(nèi)存操作、CAS、Class 相關、對象操作、線程調(diào)度、系統(tǒng)信息獲取、內(nèi)存屏障、數(shù)組操作等。今天我先來說兩個大功能:CAS 和內(nèi)存操作(和我前面的《手把手教你通過Java代碼體驗強引用、軟引用、弱引用、虛引用的區(qū)別》、《90%的程序員可能都不了解的堆外內(nèi)存》都有些關聯(lián),這是一個系列)。
CAS 操作主要涉及到下面 3 個 API。
CAS 即比較并替換,實現(xiàn)并發(fā)算法時常用到的一種技術。CAS 操作包含三個操作數(shù)——內(nèi)存位置、預期原值及新值。執(zhí)行 CAS 操作的時候,將內(nèi)存位置的值與預期原值比較,如果相匹配,那么處理器會自動將該位置值更新為新值,否則,處理器不做任何操作。我們都知道,CAS 是一條 CPU 的原子指令(cmpxchg 指令),不會造成所謂的數(shù)據(jù)不一致問題,Unsafe 提供的 CAS 方法(如 compareAndSwapXXX)底層實現(xiàn)即為 CPU 指令 cmpxchg。
CAS 在 java.util.concurrent.atomic 相關類、Java AQS、CurrentHashMap 等實現(xiàn)上有非常廣泛的應用。比如,在 AtomicInteger 的實現(xiàn)中,靜態(tài)字段 valueOffset 即為字段 value 的內(nèi)存偏移地址,valueOffset 的值在 AtomicInteger 初始化時,在靜態(tài)代碼塊中通過 Unsafe 的 objectFieldOffset 方法獲取。在 AtomicInteger 中提供的線程安全方法中,通過字段 valueOffset 的值可以定位到 AtomicInteger 對象中 value 的內(nèi)存地址,從而可以根據(jù) CAS 實現(xiàn)對 value 字段的原子操作。
比如,下圖就為某個 AtomicInteger 對象自增操作前后的內(nèi)存示意圖,對象的基地址 baseAddress=“0x110000”,通過 baseAddress+valueOffset 得到 value 的內(nèi)存地址 valueAddress=“0x11000c”;然后通過 CAS 進行原子性的更新操作,成功則返回,否則繼續(xù)重試,直到更新成功為止。
說完 CAS,我們再來說說 Unsafe 的內(nèi)存操作。
內(nèi)存操作主要有下面 9 個 API。
在《手把手教你通過Java代碼體驗強引用、軟引用、弱引用、虛引用的區(qū)別》和《90%的程序員可能都不了解的堆外內(nèi)存》兩篇文章中,我已經(jīng)講過了。在 Java 中創(chuàng)建的對象都處于堆內(nèi)內(nèi)存(heap)中,堆內(nèi)內(nèi)存是由 JVM 所管控的 Java 進程內(nèi)存,并且它們遵循 JVM 的內(nèi)存管理機制,JVM 會采用垃圾回收機制統(tǒng)一管理堆內(nèi)存。與之相對的是堆外內(nèi)存,存在于 JVM 管控之外的內(nèi)存區(qū)域,Java 中對堆外內(nèi)存的操作,依賴于 Unsafe 提供的操作堆外內(nèi)存的 native 方法。
使用堆外內(nèi)存的原因是:
對垃圾回收停頓的改善。由于堆外內(nèi)存是直接受操作系統(tǒng)管理而不是 JVM,所以當我們使用堆外內(nèi)存時,即可保持較小的堆內(nèi)內(nèi)存規(guī)模。從而在 GC 時減少回收停頓對于應用的影響。
提升程序 I/O 操作的性能。通常在 I/O 通信過程中,會存在堆內(nèi)內(nèi)存到堆外內(nèi)存的數(shù)據(jù)拷貝操作,對于需要頻繁進行內(nèi)存間數(shù)據(jù)拷貝且生命周期較短的暫存數(shù)據(jù),都建議存儲到堆外內(nèi)存。
我前面提到的 DirectByteBuffer,在 Netty、MINA 等 NIO 框架中應用廣泛。DirectByteBuffer 對于堆外內(nèi)存的創(chuàng)建、使用、銷毀等邏輯均由 Unsafe 提供的堆外內(nèi)存 API 來實現(xiàn)。
上圖為 DirectByteBuffer 構造函數(shù),創(chuàng)建 DirectByteBuffer 的時候,通過 Unsafe.allocateMemory 分配內(nèi)存、Unsafe.setMemory 進行內(nèi)存初始化,而后構建 Cleaner 對象用于跟蹤 DirectByteBuffer 對象的垃圾回收,以實現(xiàn)當 DirectByteBuffer 被垃圾回收時,分配的堆外內(nèi)存一起被釋放。具體的釋放就是我前面講的 PhantomReference 虛引用。
看完上述內(nèi)容,你們掌握如何分析Unsafe的CAS和內(nèi)存操作的原理的方法了嗎?如果還想學到更多技能或想了解更多相關內(nèi)容,歡迎關注億速云行業(yè)資訊頻道,感謝各位的閱讀!
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。