<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Unsafe類強大功能的原理詳解

          共 28076字,需瀏覽 57分鐘

           ·

          2021-09-30 07:45

          點擊上方 Java學習之道,選擇 設(shè)為星標

          每天18:30點,干貨準時奉上!

          來源: blog.csdn.net/weixin_43767015/article/details/104643890
          作者: 劉_Java

          Part1概述

          本文基于JDK1.8。

          Unsafe類位于rt.jar包,Unsafe類提供了硬件級別的原子操作,類中的方法都是native方法,它們使用JNI的方式訪問本地C++實現(xiàn)庫。由此提供了一些繞開JVM的更底層功能,可以提高程序效率。

          JNI:Java Native Interface。使得Java 與 本地其他類型語言(如C、C++)直接交互。

          Unsafe 是用于擴展 Java 語言表達能力、便于在更高層(Java 層)代碼里實現(xiàn)原本要在更低層(C 層)實現(xiàn)的核心庫功能用的。這些功能包括直接內(nèi)存的申請/釋放/訪問,低層硬件的 atomic/volatile 支持,創(chuàng)建未初始化對象,通過偏移量操作對象字段、方法、實現(xiàn)線程無鎖掛起和恢復等功能。

          所謂Java對象的“布局”就是在內(nèi)存里Java對象的各個部分放在哪里,包括對象的實例字段和一些元數(shù)據(jù)之類。Unsafe里關(guān)于對象字段訪問的方法把對象布局抽象出來,它提供了objectFieldOffset()方法用于獲取某個字段相對Java對象的“起始地址”的偏移量,也提供了getInt、getLong、getObject之類的方法可以使用前面獲取的偏移量來訪問某個Java對象的某個字段。

          Unsafe作用可以大致歸納為:

          • 內(nèi)存管理,包括分配內(nèi)存、釋放內(nèi)存等。
          • 非常規(guī)的對象實例化。
          • 操作類、對象、變量。
          • 自定義超大數(shù)組操作。
          • 多線程同步。包括鎖機制、CAS操作等。
          • 線程掛起與恢復。
          • 內(nèi)存屏障。

          Part2API詳解

          Unsafe中一共有82個public native修飾的方法,還有幾十個基于這82個public native方法的其他方法,一共有114個方法。

          2.1 初始化方法

          我們可以直接在源碼里面看到,Unsafe是單例模式的類:

          private static final Unsafe theUnsafe;
          //構(gòu)造器私有
          private Unsafe() {
          }
          //靜態(tài)塊初始化
          static {
              Reflection.registerMethodsToFilter(Unsafe.classnew String[]{"getUnsafe"});
              theUnsafe = new Unsafe();
          }
          //靜態(tài)方法獲取實例
          @CallerSensitive
          public static Unsafe getUnsafe() {
              Class var0 = Reflection.getCallerClass();
              if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
                  throw new SecurityException("Unsafe");
              } else {
                  return theUnsafe;
              }
          }

          從上面的代碼知道,好像是可以通過getUnsafe()方法獲取實例,但是如果我們調(diào)用該方法會得到一個異常:

          java.lang.SecurityException: Unsafe

           at sun.misc.Unsafe.getUnsafe(Unsafe.java:90)
           //……………………

          實際上我們可以看到getUnsafe()方法上有個 @CallerSensitive 注解,就是因為這個注解,在執(zhí)行時候需要做權(quán)限判斷:只有由主類加載器(BootStrap classLoader)加載的類才能調(diào)用這個類中的方法(比如rt.jar中的類,就可以調(diào)用該方法,原因從類名可以看出來,它是“不安全的”,怎能隨意調(diào)用,至于有哪些隱患后面會講)。顯然我們的類是由AppClassLoader加載的,所以這里直接拋出了異常。

          因此最簡單的使用方式是基于反射獲取Unsafe實例,代碼如下:

          Field f = Unsafe.class.getDeclaredField("theUnsafe");
          f.setAccessible(true);
          Unsafe unsafe = (Unsafe) f.get(null);

          2.2 類、對象和變量相關(guān)方法

          主要包括基于偏移地址獲取或者設(shè)置變量的值、基于偏移地址獲取或者設(shè)置數(shù)組元素的值、class初始化以及對象非常規(guī)的創(chuàng)建等。

          2.2.1 對象操作

          /*對象操作*/
              
              /*獲取對象字段的值*/

              //通過給定的Java變量獲取引用值。這里實際上是獲取一個Java對象o中,獲取偏移地址為offset的屬性的值,此方法可以突破修飾符的抑制,也就是無視private、protected和default修飾符。
              // 類似的方法有g(shù)etInt、getDouble等等。
              public native Object getObject(Object o, long offset);

              //此方法和上面的getObject功能類似,不過附加了'volatile'加載語義,也就是強制從主存中獲取屬性值。類似的方法有g(shù)etIntVolatile、getDoubleVolatile等等。
              // 這個方法要求被使用的屬性被volatile修飾,否則功能和getObject方法相同。
              public native Object getObjectVolatile(Object o, long offset);

              /*修改對象字段的值*/

              //設(shè)置Java對象o中偏移地址為offset的屬性的值為x,此方法可以突破修飾符的抑制,也就是無視private、protected和default修飾符。用于修改修改非基本數(shù)據(jù)類型的值。
              //類似的方法有putInt、putDouble等等,用于修改基本數(shù)據(jù)類型的值,再次不再贅述。
              public native void putObject(Object o, long offset, Object x);


              //此方法和上面的putObject功能類似,不過附加了'volatile'加載語義,也就是設(shè)置值的時候強制(JMM會保證獲得鎖到釋放鎖之間所有對象的狀態(tài)更新都會在鎖被釋放之后)更新到主存,從而保證這些變更對其他線程是可見的。
              // 類似的方法有putIntVolatile、putDoubleVolatile等等。這個方法要求被使用的屬性被volatile修飾,否則功能和putObject方法相同。
              public native void putObjectVolatile(Object o, long offset, Object x);

              //設(shè)置o對象中offset偏移地址offset對應(yīng)的Object型field的值為指定值x。這是一個有序或者有延遲的putObjectVolatile方法,并且不保證值的改變被其他線程立即看到。
              // 只有在field被volatile修飾并且期望被修改的時候使用才會生效。類似的方法有putOrderedInt和putOrderedLong。
              // 最終會設(shè)置成x,但是可能導致其他線程在之后的一小段時間內(nèi)還是可以讀到舊的值。關(guān)于該方法的更多信息可以參考并發(fā)編程網(wǎng)翻譯的一篇文章《AtomicLong.lazySet是如何工作的?》,文章地址是“http://ifeve.com/how-does-atomiclong-lazyset-work/”。
              public native void putOrderedObject(Object o, long offset, Object x);


              /*獲取對象的字段相對該對象地址的偏移量*/

              //返回給定的靜態(tài)屬性在它的類的存儲分配中的位置(偏移地址)。即相對于 className.class 的偏移量,通過這個偏移量可以快速定位字段.
              // 注意:這個方法僅僅針對靜態(tài)屬性,使用在非靜態(tài)屬性上會拋異常。
              public native long staticFieldOffset(Field f);

              //返回給定的非靜態(tài)屬性在它的類的存儲分配中的位置(偏移地址)。即字段到對象頭的偏移量,通過這個偏移量可以快速定位字段.
              // 注意:這個方法僅僅針對非靜態(tài)屬性,使用在靜態(tài)屬性上會拋異常。
              public native long objectFieldOffset(Field f);

              //返回給定的靜態(tài)屬性的位置,配合staticFieldOffset方法使用。實際上,這個方法返回值就是靜態(tài)屬性所在的Class對象的一個內(nèi)存快照
              // 注釋中說到,此方法返回的Object有可能為null,它只是一個'cookie'而不是真實的對象,不要直接使用的它的實例中的獲取屬性和設(shè)置屬性的方法,它的作用只是方便調(diào)用上面提到的像getInt(Object,long)等等的任意方法。
              public native Object staticFieldBase(Field f);

           /*創(chuàng)建對象*/   
           //繞過構(gòu)造方法、初始化代碼來非常規(guī)的創(chuàng)建對象
           public native Object allocateInstance(Class<?> cls) throws InstantiationException;

          2.2.2 class 相關(guān)

          //檢測給定的類是否需要初始化。通常需要使用在獲取一個類的靜態(tài)屬性的時候(因為一個類如果沒初始化,它的靜態(tài)屬性也不會初始化)。
          //此方法當且僅當ensureClassInitialized方法不生效的時候才返回false。
          public native boolean shouldBeInitialized(Class<?> c);

          //檢測給定的類是否已經(jīng)初始化。通常需要使用在獲取一個類的靜態(tài)屬性的時候(因為一個類如果沒初始化,它的靜態(tài)屬性也不會初始化)。
          public native void ensureClassInitialized(Class<?> c);

          //定義一個類,返回類實例,此方法會跳過JVM的所有安全檢查。默認情況下,ClassLoader(類加載器)和ProtectionDomain(保護域)實例應(yīng)該來源于調(diào)用者。
          public native Class<?> defineClass(String name, byte[] b, int off, int len, ClassLoader loader, ProtectionDomain protectionDomain);


          ///定義一個匿名類,與Java8的lambda表達式相關(guān),會用到該方法實現(xiàn)相應(yīng)的函數(shù)式接口的匿名類,可以看結(jié)尾文章鏈接。
          public native Class<?> defineAnonymousClass(Class<?> hostClass, byte[] data, Object[] cpPatches);

          2.2.3 數(shù)組元素相關(guān)

          //返回數(shù)組類型的第一個元素的偏移地址(基礎(chǔ)偏移地址)。如果arrayIndexScale方法返回的比例因子不為0,你可以通過結(jié)合基礎(chǔ)偏移地址和比例因子訪問數(shù)組的所有元素。
          // Unsafe中已經(jīng)初始化了很多類似的常量如ARRAY_BOOLEAN_BASE_OFFSET等。
          public native int arrayBaseOffset(Class<?> arrayClass);

          //返回數(shù)組單個元素的大小,數(shù)組中的元素的地址是連續(xù)的。
          // Unsafe中已經(jīng)初始化了很多類似的常量如ARRAY_BOOLEAN_INDEX_SCALE等。
          public native int arrayIndexScale(Class<?> arrayClass);

          2.3 內(nèi)存管理

          該部分包括了allocateMemory(分配內(nèi)存)、reallocateMemory(重新分配內(nèi)存)、copyMemory(拷貝內(nèi)存)、freeMemory(釋放內(nèi)存 )、getAddress(獲取內(nèi)存地址)、addressSize、pageSize、getInt(獲取內(nèi)存地址指向的整數(shù))、getIntVolatile(獲取內(nèi)存地址指向的整數(shù),并支持volatile語義)、putInt(將整數(shù)寫入指定內(nèi)存地址)、putIntVolatile(將整數(shù)寫入指定內(nèi)存地址,并支持volatile語義)、putOrderedInt(將整數(shù)寫入指定內(nèi)存地址、有序或者有延遲的方法)等方法。getXXX和putXXX包含了各種基本類型的操作。

          利用copyMemory方法,我們可以實現(xiàn)一個通用的對象拷貝方法,無需再對每一個對象都實現(xiàn)clone方法,當然這通用的方法只能做到對象淺拷貝。

          Unsafe分配的內(nèi)存,不受Integer.MAX_VALUE的限制,并且分配在非堆內(nèi)存,使用它時,需要非常謹慎:忘記手動回收時,會產(chǎn)生內(nèi)存泄露,可以通過Unsafe#freeMemory方法手動回收;非法的地址訪問時,會導致JVM崩潰。在需要分配大的連續(xù)區(qū)域、實時編程(不能容忍JVM延遲)時,可以使用它,因為直接內(nèi)存的效率會更好,詳細介紹可以去看看Java的NIO源碼,NIO中使用了這一技術(shù)。

          JDK nio包中通過ByteBuffer#allocateDirect方法分配直接內(nèi)存時,DirectByteBuffer的構(gòu)造函數(shù)中就使用到了Unsafe的allocateMemory和setMemory方法:通過Unsafe.allocateMemory分配內(nèi)存、Unsafe.setMemory進行內(nèi)存初始化,而后構(gòu)建一個虛引用Cleaner對象用于跟蹤DirectByteBuffer對象的垃圾回收,以實現(xiàn)當DirectByteBuffer被垃圾回收時,分配的堆外內(nèi)存一起被釋放(通過在Cleaner中調(diào)用Unsafe#freeMemory方法)。

          //獲取本地指針的大小(單位是byte),通常值為4(32位系統(tǒng))或者8(64位系統(tǒng))。常量ADDRESS_SIZE就是調(diào)用此方法。
          public native int addressSize();

          //獲取本地內(nèi)存的頁數(shù),此值為2的冪次方。
          //java.nio下的工具類Bits中計算待申請內(nèi)存所需內(nèi)存頁數(shù)量的靜態(tài)方法,其依賴于Unsafe中pageSize方法獲取系統(tǒng)內(nèi)存頁大小實現(xiàn)后續(xù)計算邏輯
          public native int pageSize();

          /*分配一塊新的本地內(nèi)存,
          通過bytes指定內(nèi)存塊的大小(單位是byte),返回新開辟的內(nèi)存的地址。
          可以通過freeMemory方法釋放內(nèi)存塊,
          或者通過reallocateMemory方法調(diào)整內(nèi)存塊大小。*/

          /*bytes值為負數(shù)或者過大會拋出IllegalArgumentException異常,
          如果系統(tǒng)拒絕分配內(nèi)存會拋出OutOfMemoryError異常。*/

          public native long allocateMemory(long bytes);

          //通過指定的內(nèi)存地址address重新調(diào)整本地內(nèi)存塊的大小,調(diào)整后的內(nèi)存塊大小通過bytes指定(單位為byte)。可以通過freeMemory方法釋放內(nèi)存塊,或者通過reallocateMemory方法調(diào)整內(nèi)存塊大小。
          //bytes值為負數(shù)或者過大會拋出IllegalArgumentException異常,如果系統(tǒng)拒絕分配內(nèi)存會拋出OutOfMemoryError異常。
          public native long reallocateMemory(long address, long bytes);

          //在給定的內(nèi)存塊中設(shè)置值。內(nèi)存塊的地址由對象引用o和偏移地址共同決定,如果對象引用o為null,offset就是絕對地址。第三個參數(shù)就是內(nèi)存塊的大小,如果使用allocateMemory進行內(nèi)存開辟的話,這里的值應(yīng)該和allocateMemory的參數(shù)一致。value就是設(shè)置的固定值,一般為0(這里可以參考netty的DirectByteBuffer)。
          //一般而言,o為null,所以有個重載方法是public native void setMemory(long offset, long bytes, byte value);,等效于setMemory(null, long offset, long bytes, byte value);。
          public native void setMemory(Object o, long offset, long bytes, byte value);

          //釋放內(nèi)存
          public native void freeMemory(long address);

          2.4 多線程同步

          主要包括監(jiān)視器鎖定、解鎖以及CAS相關(guān)的方法。這部分包括了monitorEnter、tryMonitorEnter、monitorExit、compareAndSwapInt、compareAndSwap等方法。其中monitorEnter、tryMonitorEnter、monitorExit已經(jīng)被標記為deprecated,不建議使用。

          Unsafe類的CAS操作可能是用的最多的,它為Java的鎖機制提供了一種新的解決辦法,比如AtomicInteger等類都是通過該方法來實現(xiàn)的。這是一種樂觀鎖,通常認為在大部分情況下不出現(xiàn)競態(tài)條件,如果操作失敗,會不斷重試直到成功。

          //鎖定對象,必須通過monitorExit方法才能解鎖。此方法經(jīng)過實驗是可以重入的,也就是可以多次調(diào)用,然后通過多次調(diào)用monitorExit進行解鎖。
              @Deprecated
              public native void monitorEnter(Object o);

              //解鎖對象,前提是對象必須已經(jīng)調(diào)用monitorEnter進行加鎖,否則拋出IllegalMonitorStateException異常。
              @Deprecated
              public native void monitorExit(Object o);

              //嘗試鎖定對象,如果加鎖成功返回true,否則返回false。必須通過monitorExit方法才能解鎖。
              @Deprecated
              public native boolean tryMonitorEnter(Object o);

              //針對Object對象進行CAS操作。即是對應(yīng)Java變量引用o,原子性地更新o中偏移地址為offset的屬性的值為x,當且僅的偏移地址為offset的屬性的當前值為expected才會更新成功返回true,否則返回false。
              //o:目標Java變量引用。offset:目標Java變量中的目標屬性的偏移地址。expected:目標Java變量中的目標屬性的期望的當前值。x:目標Java變量中的目標屬性的目標更新值。
              //類似的方法有compareAndSwapInt和compareAndSwapLong,在Jdk8中基于CAS擴展出來的方法有g(shù)etAndAddInt、getAndAddLong、getAndSetInt、getAndSetLong、getAndSetObject,它們的作用都是:通過CAS設(shè)置新的值,返回舊的值。
              public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object x);
              
              //獲取對象obj 中偏移量為offset 的變量volatile語義的當前值,并設(shè)置變量volatile 語義的值為update
              long getAndSetLong(Object obj, long offset, long update)

              //獲取對象obj同中偏移量為offset 的變量volatile語義的當前值,并設(shè)置變量值為原始值+addValue
              long getAndAddLong(Object obj, long offset, long addValue)

          2.5 線程的掛起和恢復

          這部分包括了park、unpark等方法。

          將一個線程進行掛起是通過park方法實現(xiàn)的,調(diào)用 park后,線程將一直阻塞直到超時或者中斷等條件出現(xiàn)。unpark可以終止一個掛起的線程,使其恢復正常。整個并發(fā)框架中對線程的掛起操作被封裝在 LockSupport類中,LockSupport類中有各種版本pack方法,但最終都調(diào)用了Unsafe.park()方法。

          Java8的新鎖StampedLock使用該系列方法。

          //釋放被park阻塞的線程,也可以被使用來終止一個先前調(diào)用park導致的阻塞,即這兩個方法的調(diào)用順序可以是先unpark再park。
              public native void unpark(Object thread);

              //阻塞當前線程直到一個unpark方法出現(xiàn)(被調(diào)用)、一個用于unpark方法已經(jīng)出現(xiàn)過(在此park方法調(diào)用之前已經(jīng)調(diào)用過)、線程被中斷或者time時間到期(也就是阻塞超時)。
              // 在time非零的情況下,如果isAbsolute為true,time是相對于新紀元之后的毫秒,否則time表示納秒。
              public native void park(boolean isAbsolute, long time);

          2.6 內(nèi)存屏障

          這部分包括了loadFence、storeFence、fullFence等方法。這是在Java 8新引入的,用于定義內(nèi)存屏障,避免代碼重排序。如果你了解JVM的volatile、鎖的內(nèi)存寓意,那么理解“內(nèi)存屏障”這幾個字應(yīng)該不會太難,這里只是把它包裝成了Java代碼。

          loadFence() 表示該方法之前的所有l(wèi)oad操作在內(nèi)存屏障之前完成。同理storeFence()表示該方法之前的所有store操作在內(nèi)存屏障之前完成。fullFence()表示該方法之前的所有l(wèi)oad、store操作在內(nèi)存屏障之前完成。

          //在該方法之前的所有讀操作,一定在load屏障之前執(zhí)行完成。
              public native void loadFence();

              //在該方法之前的所有寫操作,一定在store屏障之前執(zhí)行完成
              public native void storeFence();

              //在該方法之前的所有讀寫操作,一定在full屏障之前執(zhí)行完成,這個內(nèi)存屏障相當于上面兩個(load屏障和store屏障)的合體功能。
              public native void fullFence();

          2.7 其他

          //獲取系統(tǒng)的平均負載值,loadavg這個double數(shù)組將會存放負載值的結(jié)果,nelems決定樣本數(shù)量,nelems只能取值為1到3,分別代表最近1、5、15分鐘內(nèi)系統(tǒng)的平均負載。
              //如果無法獲取系統(tǒng)的負載,此方法返回-1,否則返回獲取到的樣本數(shù)量(loadavg中有效的元素個數(shù))。實驗中這個方法一直返回-1,其實完全可以使用JMX中的相關(guān)方法替代此方法。
              public native int getLoadAverage(double[] loadavg, int nelems);

              //繞過檢測機制直接拋出異常。這讓我們可以做些特別的事。
              public native void throwException(Throwable ee);

          Part3應(yīng)用

          3.0 根據(jù)偏移量(指針)修改屬性值

          public class TestUnSafe {
              static final Unsafe UNSAFE;

              //要更新的字段
              private volatile long state;
              
              //記錄字段的偏移量
              private static final long stateOffset;

              /**
               * 靜態(tài)塊初始化unsafe,并且獲取state字段的偏移量
               */

              static {
                  try {
                      //反射獲取unsafe
                      Field f = Unsafe.class.getDeclaredField("theUnsafe");
                      f.setAccessible(true);
                      UNSAFE = (Unsafe) f.get(null);
                      //獲取偏移量
                      stateOffset = UNSAFE.objectFieldOffset(TestUnSafe.class.getDeclaredField("state"));
                  } catch (Exception ex) {
                      throw new Error(ex);
                  }
              }

              public TestUnSafe(long state) {
                  this.state = state;
              }

              public static void main(String[] args) {
                  TestUnSafe testUnSafe = new TestUnSafe(0);
                  //嘗試更改變量值
                  boolean b = UNSAFE.compareAndSwapLong(testUnSafe, stateOffset, testUnSafe.state, 2);
                  System.out.println(b);
                  System.out.println(testUnSafe.state);
              }
          }

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

          我們通常所用到的創(chuàng)建對象的方式,有直接new創(chuàng)建、也有反射創(chuàng)建,其本質(zhì)都是調(diào)用相應(yīng)的構(gòu)造器,而使用有參構(gòu)造函數(shù)時,必須傳遞相應(yīng)個數(shù)的參數(shù)才能完成對象實例化。

          而Unsafe中提供allocateInstance方法,僅通過Class對象就可以創(chuàng)建此類的實例對象,而且不需要調(diào)用其構(gòu)造函數(shù)、初始化代碼、JVM安全檢查等。并且它抑制修飾符檢測,也就是即使構(gòu)造器是private修飾的也能通過此方法實例化,只需提類對象即可創(chuàng)建相應(yīng)的對象。

          由于這種特性,allocateInstance在java.lang.invoke、Objenesis(提供繞過類構(gòu)造器的對象生成方式)、Gson(反序列化時用到)中都有相應(yīng)的應(yīng)用。在Gson反序列化時,如果類有默認構(gòu)造函數(shù),則通過反射調(diào)用默認構(gòu)造函數(shù)創(chuàng)建實例,否則通過UnsafeAllocator來實現(xiàn)對象實例的構(gòu)造,UnsafeAllocator通過調(diào)用Unsafe的allocateInstance實現(xiàn)對象的實例化,保證在目標類無默認構(gòu)造函數(shù)時,反序列化不夠影響。

          案例:

          public class UnsafeTest {
              private static Unsafe UNSAFE;

              static {
                  try {
                      Field field = Unsafe.class.getDeclaredField("theUnsafe");
                      field.setAccessible(true);
                      UNSAFE = (Unsafe) field.get(null);
                  } catch (Exception ignored) {
                  }
              }

              public static void main(String[] args) {
                  //reflect();
                  unsafe();
              }

              /**
               * 反射測試,注釋掉無參構(gòu)造器,方法報錯;開放注釋,方法執(zhí)行成功,type字段有值。
               */

              public static void reflect() {
                  /*如果沒有無參構(gòu)造器,該反射會拋出異常,其內(nèi)部還是使用的new關(guān)鍵字*/
                  try {
                      Class<?> aClass = Class.forName("com.thread.test.juc.unsafe.User");
                      Constructor<?> constructor = aClass.getDeclaredConstructor();
                      constructor.setAccessible(true);
                      User o = (User) constructor.newInstance(null);
                      System.out.println(o);
                      /*值為vip,正常*/
                      System.out.println(o.type);
                      System.out.println(o.age);
                  } catch (Exception e) {
                      e.printStackTrace();
                  }
              }

              /**
               * UNSAFE測試,注釋掉無參構(gòu)造器,照樣成功構(gòu)造對象,但是type字段為null。這就是沒有走構(gòu)造器的后果之一:沒有對字段進行初始化
               */

              public static void unsafe() {
                  try {
                      /*不需要相應(yīng)的構(gòu)造器即可創(chuàng)建對象*/
                      User user = (User) UNSAFE.allocateInstance(User.class);
                      user.setName("user1");
                      System.out.println("instance: " + user);
                      user.test();
                      /*通過unsafe設(shè)置屬性值*/
                      Field name = user.getClass().getDeclaredField("name");
                      UNSAFE.putObject(user, UNSAFE.objectFieldOffset(name), "user2");
                      user.test();

                      /*值為null,說明unsafe并沒有初始化字段。*/
                      System.out.println(user.type);
                      System.out.println(user.age);
                  } catch (Exception e) {
                      e.printStackTrace();
                  }
              }
          }

          class User {
              public String type = "VIP";
              public int age = 20;
              private String name;

              public void setName(String name) {
                  this.name = name;
              }

              public void test() {
                  System.err.println("hello,world " + name);
              }

              /*private User() {
                  System.out.println("constructor");
              }*/


              private User(String name) {
                  this.name = name;
              }
          }

          注意:UNSAFE測試時,其vip字段并沒有獲取到值。實際上一個new操作,編譯成指令后(javap -v xx.class)是3條:

          * 第一條指令的意思是根據(jù)類型分配一塊內(nèi)存區(qū)域

          • 第二條指令是把第一條指令返回的內(nèi)存地址壓入操作數(shù)棧頂
          • 第三條指令是調(diào)用類的構(gòu)造函數(shù),對字段進行顯示初始化操作。

          Unsafe.allocateInstance()方法只做了第一步和第二步,即分配內(nèi)存空間,返回內(nèi)存地址,沒有做第三步調(diào)用構(gòu)造函數(shù)。所以Unsafe.allocateInstance()方法創(chuàng)建的對象都是只有初始值,沒有默認值也沒有構(gòu)造函數(shù)設(shè)置的值,因為它完全沒有使用new機制,直接操作內(nèi)存創(chuàng)建了對象。

          3.2 超長數(shù)組操作

          前面講的arrayBaseOffset與arrayIndexScale配合起來使用,就可以定位數(shù)組中每個元素在內(nèi)存中的位置。putByte和getByte則可以獲取指定位置的byte數(shù)據(jù)。

          常規(guī)Java的數(shù)組最大值為Integer.MAX_VALUE,但是使用Unsafe類的內(nèi)存分配方法可以實現(xiàn)超大數(shù)組。實際上這樣的數(shù)據(jù)就可以認為是C數(shù)組,因此需要注意在合適的時間釋放內(nèi)存。

          下例創(chuàng)建分配一段連續(xù)的內(nèi)存(數(shù)組),它的容量是Java允許最大容量的兩倍(有可能造成JVM崩潰):

          class SuperArray {
              private final static int BYTE = 1;
              private long size;
              private long address;
              private static Unsafe unsafe;

              static {
                  try {
                      Field field = Unsafe.class.getDeclaredField("theUnsafe");
                      field.setAccessible(true);
                      unsafe = (Unsafe) field.get(null);
                  } catch (Exception e) {
                  }
              }

              public SuperArray(long size) {
                  this.size = size;
                  //得到分配內(nèi)的起始地址
                  address = unsafe.allocateMemory(size * BYTE);
              }

              public void set(long i, byte value) {
                  //設(shè)置值
                  unsafe.putByte(address + i * BYTE, value);
              }

              public int get(long idx) {
                  //獲取值
                  return unsafe.getByte(address + idx * BYTE);
              }

              public long size() {
                  return size;
              }

              public static void main(String[] args) {
                  //兩倍Integer.MAX_VALUE長度
                  long SUPER_SIZE = (long) Integer.MAX_VALUE * 2;
                  SuperArray array = new SuperArray(SUPER_SIZE);
                  System.out.println("Array size:" + array.size()); // 4294967294
                  int sum = 0;
                  for (int i = 0; i < 100; i++) {
                      array.set((long) Integer.MAX_VALUE + i, (byte3);
                      sum += array.get((long) Integer.MAX_VALUE + i);
                  }
                  System.out.println("Sum of 100 elements:" + sum);  // 300
              }
          }

          3.3 包裝受檢異常為運行時異常

          unsafe.throwException(new IOException());

          3.4 運行時動態(tài)創(chuàng)建類

          標準的動態(tài)加載類的方法是Class.forName()(在編寫jdbc程序時,記憶深刻),使用Unsafe也可以動態(tài)加載java 的class文件。操作方式就是將.class文件讀取到字節(jié)數(shù)據(jù)組中,并將其傳到defineClass方法中。

          public class CreateClass {
              private static Unsafe unsafe;
              static {
                  try {
                      Field field = Unsafe.class.getDeclaredField("theUnsafe");
                      field.setAccessible(true);
                      unsafe = (Unsafe) field.get(null);
                  } catch (Exception e) {
                      e.printStackTrace();
                  }
              }
              //Method to read .class file
              private static byte[] getClassContent() throws Exception {
                  File f = new File("target/classes/com/thread/test/juc/unsafe/A.class");
                  FileInputStream input = new FileInputStream(f);
                  byte[] content = new byte[(int) f.length()];
                  input.read(content);
                  input.close();
                  return content;
              }
              public static void main(String[] args) throws Exception {
                  //Sample code to creat classes
                  byte[] classContents = getClassContent();
                  Class c = unsafe.defineClass(null, classContents, 0, classContents.length, CreateClass.class.getClassLoader(), null);
                  c.getMethod("a").invoke(c.newInstance());   //aaaa
              }
          }
          class A {
              public void a() {
                  System.out.println("aaaa");
              }
          }

          3.5 實現(xiàn)淺克隆

          使用直接獲取內(nèi)存的方式實現(xiàn)淺克隆。把一個對象的字節(jié)碼拷貝到內(nèi)存的另外一個地方,然后再將這個對象轉(zhuǎn)換為被克隆的對象類型。為了表述方便,用S代表要克隆的對象,D表示克隆后的對象,SD表示S的內(nèi)存地址,DD表示D的內(nèi)存地址,SIZE表示該對象在內(nèi)存中的大小。

          • 獲取原對象的所在的內(nèi)存地址SD。
          • 計算原對象在內(nèi)存中的大小SIZE。
          • 新分配一塊內(nèi)存,大小為原對象大小SIZE,記錄新分配內(nèi)存的地址DD。
          • 從原對象內(nèi)存地址SD處復制大小為SIZE的內(nèi)存,復制到DD處。
          • DD處的SIZE大小的內(nèi)存就是原對象的淺克隆對象,強制轉(zhuǎn)換為源對象類型就可以了。

          Part4總結(jié)和注意

          從上面的介紹中,我們可以看到Unsafe非常強大和有趣的功能,但是實際上官方是不推薦我們在代碼中直接使用Unsafe類的。甚至從命名就能看出來"Unsafe"——那肯定就是不安全的意思啦。那么什么不安全呢?我們知道C或C++是可以直接操作指針的,指針操作是非常不安全的,這也是Java“去除”指針的原因。

          回到Unsafe類,類中包含大量操作指針偏移量的方法,偏移量要自己計算,如若使用不當,會對程序帶來許多不可控的災難,JVM直接崩潰虧。因此對它的使用我們需要慎之又慎,生產(chǎn)級別的代碼就更不應(yīng)該使用Unsafe類了。

          另外Unsafe類還有很多自主操作內(nèi)存的方法,這些都是直接內(nèi)存,而使用的這些內(nèi)存不受JVM管理(無法被GC),需要手動管理,一旦出現(xiàn)疏忽很有可能成為內(nèi)存泄漏的源頭。

          盡管Unsafe是“不安全的”,但是它的“應(yīng)用”卻很廣泛。Unsafe在JUC(java.util.concurrent)包中大量使用(主要是CAS),在netty中方便使用直接內(nèi)存,還有一些高并發(fā)的交易系統(tǒng)為了提高CAS的效率也有可能直接使用到Unsafe,比如Hadoop、Kafka、akka。

          總而言之,Unsafe類是一把雙刃劍。或許這里的“不安全”只是針對像我們這些“菜鳥”而提出的警告吧!::>_<::

          -- END --

           | 更多精彩文章 -



          加我微信,交個朋友
          長按/掃碼添加↑↑↑

          瀏覽 77
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲AV无码成人精品区国产 | 内射视频免费看 | 亚洲777777 | 先锋白领AV| 一本大道一区二区三区 |