日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關咨詢
選擇下列產(chǎn)品馬上在線溝通
服務時間:8:30-17:00
你可能遇到了下面的問題
關閉右側工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
聊聊Unsafe的一些使用技巧

本文轉載自微信公眾號「Kirito的技術分享」,作者kiritomoe 。轉載本文請聯(lián)系Kirito的技術分享公眾號。

創(chuàng)新互聯(lián)建站服務項目包括鹽田網(wǎng)站建設、鹽田網(wǎng)站制作、鹽田網(wǎng)頁制作以及鹽田網(wǎng)絡營銷策劃等。多年來,我們專注于互聯(lián)網(wǎng)行業(yè),利用自身積累的技術優(yōu)勢、行業(yè)經(jīng)驗、深度合作伙伴關系等,向廣大中小型企業(yè)、政府機構等提供互聯(lián)網(wǎng)行業(yè)的解決方案,鹽田網(wǎng)站推廣取得了明顯的社會效益與經(jīng)濟效益。目前,我們服務的客戶以成都為中心已經(jīng)輻射到鹽田省份的部分城市,未來相信會繼續(xù)擴大服務區(qū)域并繼續(xù)獲得客戶的支持與信任!

前言

記得初學 Java 那會,剛學完語法基礎,就接觸到了反射這個 Java 提供的特性,盡管在現(xiàn)在看來,這是非?;A的知識點,但那時候無疑是興奮的,瞬間覺得自己脫離了“Java 初學者”的隊伍。隨著工作經(jīng)驗的積累,我也逐漸學習到了很多類似的讓我為之而興奮的知識點,Unsafe 的使用技巧無疑便是其中一個。

sun.misc.Unsafe 是 JDK 原生提供的一個工具類,包含了很多在 Java 語言看來很 cool 的操作,例如內(nèi)存分配與回收、CAS 操作、類實例化、內(nèi)存屏障等。正如其命名一樣,由于其可以直接操作內(nèi)存,執(zhí)行底層系統(tǒng)調(diào)用,其提供的操作也是比較危險的。Unsafe 在擴展 Java 語言表達能力、便于在更高層(Java層)代碼里實現(xiàn)原本要在更低層(C層)實現(xiàn)的核心庫功能上起到了很大的作用。

從 JDK9 開始,Java 模塊化設計的限制,使得非標準庫的模塊都無法訪問到 sun.misc.Unsafe。但在 JDK8 中,我們?nèi)匀豢梢灾苯硬僮?Unsafe,再不學習,后面可能就沒機會了。

使用 Unsafe

Unsafe 被設計的初衷,并不是希望被一般開發(fā)者調(diào)用,所以我們不能通過 new 或者工廠方法去實例化 Unsafe 對象,通??梢圆捎梅瓷涞姆椒ǐ@取到 Unsafe 實例:

 
 
 
 
  1. public static final Unsafe unsafe = getUnsafe();
  2. static sun.misc.Unsafe getUnsafe() {
  3.     try {
  4.         Field field = Unsafe.class.getDeclaredField("theUnsafe");
  5.         field.setAccessible(true);
  6.         return  (Unsafe) field.get(null);
  7.     } catch (Exception e) {
  8.         throw new RuntimeException(e);
  9.     }
  10. }

拿到之后,便可以用這個全局的單例對象去為所欲為了。

功能概覽

圖片來源于網(wǎng)絡,我直接借用過來了。上圖包含了 Unsafe 的眾多功能,還算全面。如果全部介紹,文章篇幅會過長,形式難免會流水賬,我打算結合我的一些項目經(jīng)驗以及一些比賽經(jīng)驗,從實踐角度聊聊 Unsafe 的一些使用技巧。

內(nèi)存分配&存取

Java 其實也可以像 C++ 那樣直接操作內(nèi)存,借助 Unsafe 就可以。讓我們先來看一個 ByteBuffer 的示例,我們將會開辟一個 16 字節(jié)的內(nèi)存空間,先后寫入并讀取 4 個 int 類型的數(shù)據(jù)。

 
 
 
 
  1. public static void testByteBuffer() {
  2.     ByteBuffer directBuffer = ByteBuffer.allocateDirect(16);
  3.     directBuffer.putInt(1);
  4.     directBuffer.putInt(2);
  5.     directBuffer.putInt(3);
  6.     directBuffer.putInt(4);
  7.     directBuffer.flip();
  8.     System.out.println(directBuffer.getInt());
  9.     System.out.println(directBuffer.getInt());
  10.     System.out.println(directBuffer.getInt());
  11.     System.out.println(directBuffer.getInt());
  12. }

熟悉 nio 操作的同學對上面的示例應該不會感到陌生,這是很基礎也是很標準的內(nèi)存使用方式。那換做是 Unsafe 怎么實現(xiàn)同樣的效果的?

 
 
 
 
  1. public static void testUnsafe0() {
  2.     Unsafe unsafe = Util.unsafe;
  3.     long address = unsafe.allocateMemory(16);
  4.     unsafe.putInt(address, 1);
  5.     unsafe.putInt(address + 4, 2);
  6.     unsafe.putInt(address + 8, 3);
  7.     unsafe.putInt(address + 12, 4);
  8.     System.out.println(unsafe.getInt(address));
  9.     System.out.println(unsafe.getInt(address + 4));
  10.     System.out.println(unsafe.getInt(address + 8));
  11.     System.out.println(unsafe.getInt(address + 12));
  12. }

兩段代碼輸出結果一致:

 
 
 
 
  1. 1
  2. 2
  3. 3
  4. 4

下面針對使用到的 Unsafe 的 API,逐個介紹:

 
 
 
 
  1. public native long allocateMemory(long var1);

這個 native 方法分配的是堆外內(nèi)存,返回的 long 類型數(shù)值,便是內(nèi)存的首地址,可以作為 Unsafe 其他 API 的入?yún)?。你如果見過 DirectByteBuffer 的源碼,會發(fā)現(xiàn)其實它內(nèi)部就是使用 Unsafe 封裝的。說到 DirectByteBuffer,這里額外提一句,ByteBuffer.allocateDirect 分配的堆外內(nèi)存會受到 -XX:MaxDirectMemorySize 的限制,而 Unsafe 分配的堆外內(nèi)存則不會受到限制,當然啦,也不會受到 -Xmx 的限制。如果你正在參加什么比賽并且受到了什么啟發(fā),可以把“爺懂了”打在公屏上。

看到另外兩個 API putInt 和 getInt ,你應當會意識到,肯定會有其他字節(jié)操作的 API,例如 putByte/putShort/putLong ,當然 put 和 get 也是成對出現(xiàn)的。這一系列 API 里面也有注意點,建議需要成對的使用,否則可能會因為字節(jié)序問題,導致解析失敗??梢钥聪旅娴睦樱?/p>

 
 
 
 
  1. public static void testUnsafe1() {
  2.     ByteBuffer directBuffer = ByteBuffer.allocateDirect(4);
  3.     long directBufferAddress = ((DirectBuffer)directBuffer).address();
  4.     System.out.println("Unsafe.putInt(1)");
  5.     Util.unsafe.putInt(directBufferAddress, 1);
  6.     System.out.println("Unsafe.getInt() == " + Util.unsafe.getInt(directBufferAddress));
  7.     directBuffer.position(0);
  8.     directBuffer.limit(4);
  9.     System.out.println("ByteBuffer.getInt() == " + directBuffer.getInt());
  10.     directBuffer.position(0);
  11.     directBuffer.limit(4);
  12.     System.out.println("ByteBuffer.getInt() reverseBytes == " + Integer.reverseBytes(directBuffer.getInt()));
  13. }

輸出如下:

 
 
 
 
  1. Unsafe.putInt(1)
  2. Unsafe.getInt() == 1
  3. ByteBuffer.getInt() == 16777216
  4. ByteBuffer.getInt() reverseBytes == 1

可以發(fā)現(xiàn)當我們使用 Unsafe 進行 putInt,再使用 ByteBuffer 進行 getInt,結果會不符合預期,需要對結果進行字節(jié)序變化之后,才恢復正確。這其實是因為,ByteBuffer 內(nèi)部判斷了當前操作系統(tǒng)的字節(jié)序,對于 int 這種多字節(jié)的數(shù)據(jù)類型,我的測試機器使用大端序存儲,而 Unsafe 默認以小短序存儲導致。如果你拿捏不準,建議配套使用寫入和讀取 API,以避免字節(jié)序問題。對字節(jié)序不了解的同學可以參考我的另外一篇文章:《“字節(jié)序”是個什么鬼》。

內(nèi)存復制

內(nèi)存復制在實際應用場景中還是很常見的需求,例如上一篇文章我剛介紹過的,堆內(nèi)內(nèi)存寫入磁盤時,需要先復制到堆外內(nèi)存,再例如我們做內(nèi)存聚合時,需要緩沖一部分數(shù)據(jù),也會涉及到內(nèi)存復制。你當然也可以通過 ByteBuffer 或者 set/get 去進行操作,但肯定不如 native 方法來的高效。Unsafe 提供了內(nèi)存拷貝的 native 方法,可以實現(xiàn)堆內(nèi)到堆內(nèi)、堆外到堆外、堆外和堆內(nèi)互相拷貝,總之就是哪兒到哪兒都可以拷貝。

 
 
 
 
  1. public native void copyMemory(Object src, long offset, Object dst ,long dstOffset, long size);

對于堆內(nèi)內(nèi)存來說,我們可以直接給 src 傳入對象數(shù)組的首地址,并且指定 offset 為對應數(shù)組類型的偏移量,可以通過 arrayBaseOffset 方法獲取堆內(nèi)內(nèi)存存儲對象的偏移量

 
 
 
 
  1. public native int arrayBaseOffset(Class var1);

例如獲取 byte[] 的固定偏移量可以這樣操作:unsafe.arrayBaseOffset(byte[].class)

對于堆外內(nèi)存來說,會更加直觀一點,dst 設為 null,dstOffset 設置為 Unsafe 獲取的內(nèi)存地址即可。

堆內(nèi)內(nèi)存復制到堆外內(nèi)存的示例代碼:

 
 
 
 
  1. public static void unsafeCopyMemory()  {
  2.     ByteBuffer heapBuffer = ByteBuffer.allocate(4);
  3.     ByteBuffer directBuffer = ByteBuffer.allocateDirect(4);
  4.     heapBuffer.putInt(1234);
  5.     long address = ((DirectBuffer)directBuffer).address();
  6.     Util.unsafe.copyMemory(heapBuffer.array(), 16, null, address, 4);
  7.     directBuffer.position(0);
  8.     directBuffer.limit(4);
  9.     System.out.println(directBuffer.getInt());
  10. }

在實際應用中,大多數(shù) ByteBuffer 相關的源碼在涉及到內(nèi)存復制時,都使用了 copyMemory 方法。

非常規(guī)實例化對象

在 JDK9 模塊化之前,如果不希望將一些類開放給其他用戶使用,或者避免被隨意實例化(單例模式),通常有兩個常見做法

案例一:私有化構造器

 
 
 
 
  1. public class PrivateConstructorFoo {
  2.     private PrivateConstructorFoo() {
  3.         System.out.println("constructor method is invoked");
  4.     }
  5.     public void hello() {
  6.         System.out.println("hello world");
  7.     }
  8. }

如果希望實例化該對象,第一時間想到的可能是反射創(chuàng)建

 
 
 
 
  1. public static void reflectConstruction() {
  2.   PrivateConstructorFoo privateConstructorFoo = PrivateConstructorFoo.class.newInstance();
  3.   privateConstructorFoo.hello();
  4. }

不出所料,我們獲得了一個異常

 
 
 
 
  1. java.lang.IllegalAccessException: Class io.openmessaging.Main can not access a member of class moe.cnkirito.PrivateConstructorFoo with modifiers "private"

稍作調(diào)整,調(diào)用構造器創(chuàng)建實例

 
 
 
 
  1. public static void reflectConstruction2() {
  2.    Constructor constructor = PrivateConstructorFoo.class.getDeclaredConstructor();
  3.    constructor.setAccessible(true);
  4.    PrivateConstructorFoo privateConstructorFoo = constructor.newInstance();
  5.    privateConstructorFoo.hello();
  6. }

it works!輸出如下:

 
 
 
 
  1. constructor method is invoked
  2. hello world

當然,Unsafe 也提供了 allocateInstance 方法

 
 
 
 
  1. public native Object allocateInstance(Class var1) throws InstantiationException;

也可以實現(xiàn)實例化,而且更為直觀

 
 
 
 
  1. public static void allocateInstance() throws InstantiationException {
  2.     PrivateConstructorFoo privateConstructorFoo = (PrivateConstructorFoo) Util.unsafe.allocateInstance(PrivateConstructorFoo.class);
  3.     privateConstructorFoo.hello();
  4. }

同樣 works!輸出如下:

 
 
 
 
  1. hello world

注意這里有一個細節(jié),allocateInstance 沒有觸發(fā)構造方法。

案例二:package level 實例

 
 
 
 
  1. package moe.cnkirito;
  2. class PackageFoo {
  3.     public void hello() {
  4.         System.out.println("hello world");
  5.     }
  6. }

注意,這里我定義了一個 package 級別可訪問的對象 PackageFoo,只有 moe.cnkirito 包下的類可以訪問。

我們同樣先嘗試使用反射

 
 
 
 
  1. package com.bellamm;
  2. public static void reflectConstruction() {
  3.   Class aClass = Class.forName("moe.cnkirito.PackageFoo");
  4.   aClass.newInstance();
  5. }

得到了意料之中的報錯:

 
 
 
 
  1. java.lang.IllegalAccessException: Class io.openmessaging.Main can not access a member of class moe.cnkirito.PackageFoo with modifiers ""

再試試 Unsafe 呢?

 
 
 
 
  1. package com.bellamm;
  2. public static void allocateInstance() throws Exception{
  3.     Class fooClass = Class.forName("moe.cnkirito.PackageFoo");
  4.     Object foo = Util.unsafe.allocateInstance(fooClass);
  5.     Method helloMethod = fooClass.getDeclaredMethod("hello");
  6.     helloMethod.setAccessible(true);
  7.     helloMethod.invoke(foo);
  8. }

由于在 com.bellamm 包下,我們甚至無法在編譯期定義 PackageFoo 類,只能通過反射機制在運行時,獲取 moe.cnkirito.PackageFoo 的方法,配合 Unsafe 實例化,最終實現(xiàn)調(diào)用,成功輸出 hello world。

我們花了這么大的篇幅進行實驗來說明了兩種限制案例,以及 Unsafe 的解決方案,還需要有實際的應用場景佐證 Unsafe#allocateInstance 的價值。我簡單列舉兩個場景:

  1. 序列化框架在使用反射無法創(chuàng)建對象時,可以嘗試使用 Unsafe 創(chuàng)建,作為兜底邏輯。
  2. 獲取包級別保護的類,再借助于反射機制,可以魔改一些源碼實現(xiàn)或者調(diào)用一些 native 方法,此法慎用,不建議在生產(chǎn)使用。

示例代碼:動態(tài)修改堆外內(nèi)存限制,覆蓋 JVM 啟動參數(shù):-XX:MaxDirectMemorySize

 
 
 
 
  1. private void hackMaxDirectMemorySize() {
  2.      try {
  3.          Field directMemoryField = VM.class.getDeclaredField("directMemory");
  4.          directMemoryField.setAccessible(true);
  5.          directMemoryField.set(new VM(), 8L * 1024 * 1024 * 1024);
  6.          Object bits = Util.unsafe.allocateInstance(Class.forName("java.nio.Bits"));
  7.          Field maxMemory = bits.getClass().getDeclaredField("maxMemory");
  8.          maxMemory.setAccessible(true);
  9.          maxMemory.set(bits, 8L * 1024 * 1024 * 1024);
  10.      } catch (Exception e) {
  11.          throw new RuntimeException(e);
  12.      }
  13.      System.out.println(VM.maxDirectMemory());
  14.  }

總結

先大概介紹這三個 Unsafe 用法吧,已經(jīng)是我個人認為比較常用的幾個 Unsafe 案例了。

Unsafe 這個東西,會用的人基本都知道不能瞎用;不會用的話,看個熱鬧,知道 Java 有這個機制總比不知道強對吧。當然,本文也介紹了一些實際場景可能必須得用 Unsafe,但更多還是出現(xiàn)在各個底層源碼之中。

如果還有讀者想看到更多騷操作的話,歡迎轉發(fā)本文,閱讀過 1500,繼續(xù)加更一期,一鍵三連,這次一定。


分享標題:聊聊Unsafe的一些使用技巧
網(wǎng)頁地址:http://www.5511xx.com/article/cojodsg.html