一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - 你一定不知道的Java Unsafe用法詳解

你一定不知道的Java Unsafe用法詳解

2022-03-07 00:48接地氣程序員 Java教程

Unsafe是位于sun.misc包下的一個類,主要提供一些用于執行低級別、不安全操作的方法,如直接訪問系統內存資源、自主管理內存資源等,下面這篇文章主要給大家介紹了關于Java Unsafe用法的相關資料,需要的朋友可以參考下

Unsafe是什么

首先我們說Unsafe類位于rt.jar里面sun.misc包下面,Unsafe翻譯過來是不安全的,這倒不是說這個類是不安全的,而是說開發人員使用Unsafe是不安全的,也就是不推薦開發人員直接使用Unsafe。而且Oracle JDK源碼包里面是沒有Unsafe的源碼的。其實JUC包里面的類大部分都用到了Unsafe,可以說Unasfe是java并發包的基石。

 

如何正確地獲取Unsafe對象

我們從源碼中看如何獲取Unsafe對象

private Unsafe() {
}

首先構造方法私有化,這就說明我們不能通過new Unsafe的方式創建Unsafe對象。

@CallerSensitive
  public static Unsafe getUnsafe() {
      Class var0 = Reflection.getCallerClass();
      if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
          throw new SecurityException("Unsafe");
      } else {
          return theUnsafe;
      }
  }

我又發現有一個靜態方法,方法名為getUnsafe,而且返回值為Unsafe,看來調用這個方法可以獲取Unsafe對象。

為此我又編寫了如下代碼測試這樣是否行得通

public static void main(String[] args) throws Exception{
      Unsafe unsafe = Unsafe.getUnsafe();
      System.out.println(unsafe);
}

誰知道控制臺竟然報錯了

你一定不知道的Java Unsafe用法詳解

看錯誤提示信息是權限方面的錯誤,但是我看AtomicBoolean類獲取Unsafe的方式就是調用getUnsafe方法,可能是只允許JDK內部的類可以通過這種方式訪問吧,這里我們不深究,再想別的辦法獲取。

繼續看源碼找突破口

Unsafe類里面第一個常量是 private static final Unsafe theUnsafe; 用static和final修飾而且沒有直接賦值,這就說明肯定有靜態代碼塊對theUnsafe賦值了,然后再類的底部發現了。

static {
      registerNatives();
      Reflection.registerMethodsToFilter(Unsafe.class, new String[]{"getUnsafe"});
      theUnsafe = new Unsafe();
      ARRAY_BOOLEAN_BASE_OFFSET = theUnsafe.arrayBaseOffset(boolean[].class);
      ARRAY_BYTE_BASE_OFFSET = theUnsafe.arrayBaseOffset(byte[].class);
      ARRAY_SHORT_BASE_OFFSET = theUnsafe.arrayBaseOffset(short[].class);
      ARRAY_CHAR_BASE_OFFSET = theUnsafe.arrayBaseOffset(char[].class);
      ARRAY_INT_BASE_OFFSET = theUnsafe.arrayBaseOffset(int[].class);
      ARRAY_LONG_BASE_OFFSET = theUnsafe.arrayBaseOffset(long[].class);
      ARRAY_FLOAT_BASE_OFFSET = theUnsafe.arrayBaseOffset(float[].class);
      ARRAY_DOUBLE_BASE_OFFSET = theUnsafe.arrayBaseOffset(double[].class);
      ARRAY_OBJECT_BASE_OFFSET = theUnsafe.arrayBaseOffset(Object[].class);
      ARRAY_BOOLEAN_INDEX_SCALE = theUnsafe.arrayIndexScale(boolean[].class);
      ARRAY_BYTE_INDEX_SCALE = theUnsafe.arrayIndexScale(byte[].class);
      ARRAY_SHORT_INDEX_SCALE = theUnsafe.arrayIndexScale(short[].class);
      ARRAY_CHAR_INDEX_SCALE = theUnsafe.arrayIndexScale(char[].class);
      ARRAY_INT_INDEX_SCALE = theUnsafe.arrayIndexScale(int[].class);
      ARRAY_LONG_INDEX_SCALE = theUnsafe.arrayIndexScale(long[].class);
      ARRAY_FLOAT_INDEX_SCALE = theUnsafe.arrayIndexScale(float[].class);
      ARRAY_DOUBLE_INDEX_SCALE = theUnsafe.arrayIndexScale(double[].class);
      ARRAY_OBJECT_INDEX_SCALE = theUnsafe.arrayIndexScale(Object[].class);
      ADDRESS_SIZE = theUnsafe.addressSize();
  }

第四行對theUnsafe進行了賦值。也就是說在類加載完成后Unsafe里面的theUnsafe常量就已經賦值好了Unsafe對象,如果我們想獲取Unsafe對象只要用反射拿到theUnsafe屬性就可以了。

/**
   * 獲得Unsafe
   * @throws NoSuchFieldException
   * @throws IllegalAccessException
   */
  public static Unsafe getUnsafe() throws NoSuchFieldException, IllegalAccessException {
      Field field = Unsafe.class.getDeclaredField("theUnsafe");
      //私有屬性可以訪問
      field.setAccessible(true);
      Unsafe unsafe = (Unsafe) field.get(null);
      System.out.println(unsafe);
      return unsafe;
  }

你一定不知道的Java Unsafe用法詳解

 

Unsafe實現CAS鎖

CAS是compare and swap的縮寫,中文翻譯成比較并交換。 在juc包下Atomic開頭的類都是使用的CAS鎖實現的并發條件下對一個變量賦值不覆蓋的。我們也可以自己使用Unsafe實現CAS鎖。

interface Counter{
      void increment();
      long getCounter();
  }
/**
   * 自己用unsafe實現CAS鎖
   */
  static class CasCounter implements Counter{
      private volatile long counter = 0;
      private Unsafe unsafe;
      private long offset;

      CasCounter() throws NoSuchFieldException, IllegalAccessException {
          unsafe = getUnsafe();
          //獲得該類counter屬性內存偏移量起始位置
          offset = unsafe.objectFieldOffset(CasCounter.class.getDeclaredField("counter"));
      }

      @Override
      public void increment() {
          long current = counter;
          //循環判斷是否賦值成功  第一個參數為調用本方法的對象,第二個參數為要更改屬性的內存偏移量,第三個參數是未修改之前的值,第四個參數是想要修改為那個值。
          while (!unsafe.compareAndSwapLong(this,offset,current,current+1)){
              current = counter;
          }
      }

      @Override
      public long getCounter() {
          return counter;
      }
  }

這里我們主要看unsafe.objectFieldOffset(CasCounter.class.getDeclaredField("counter"));這一行代碼,因為通過CAS對屬性值進行更改是直接在內存上進行更改的,所以我們需要拿到這個對象的counter屬性的內存偏移量。

再看increment方法,這里我們在更改之前先拿到counter的值,unsafe.compareAndSwapLong方法就是根據內存偏移量進行更改值的,第一個參數確定那個對象,第二個參數確定那個屬性,第三個參數比對要更改屬性的原值,第四個參數要更改的值。如果更改成功則返回true,更改失敗返回false。這里的邏輯是更改失敗就一直更改,知道更改成功才跳出循環。這樣就會有效的防止屬性值被覆蓋的問題。 我們寫的CasCounter類就實現了AtomicInteger類的部分功能。

 

使用Unsafe創建對象

我們都知道反射可以‘走后門'創建對象,其實Unsafe也是可以的

static class Simple{
      static {
          System.out.println("類初始化");
      }
      private long l = 0;

      public Simple(){
          this.l = 1;
          System.out.println("對象初始化");
      }

      public long get(){
          return l;
      }
}

public static void main(String[] args) throws Exception {
    Unsafe unsafe = getUnsafe();
      //相當于直接在內存中開辟一塊地址,不運行構造方法
      Simple simple = (Simple) unsafe.allocateInstance(Simple.class);
      System.out.println("通過unsafe創建對象不會運行構造方法: " + simple.get());
      System.out.println("但是可以通過對象獲得class對象" + simple.getClass());
      System.out.println("也可以拿到類加載器 " + simple.getClass().getClassLoader());
}

控制臺輸出如下

你一定不知道的Java Unsafe用法詳解

這里我們發現使用Unsafe創建對象并沒有運行構造方法,而只是將對象創建出來了。而使用反射創建對象是會運行構造方法的和使用new的方式創建對象別無二致。所以不推薦使用Unsafe創建對象。

 

Unsafe加載類

既然Unsafe是直接操作的內存那應該也可以加載類,下面我們看看Unsafe是如何加載類的。

我們先自己編寫A類

public class A
{
private int i = 0;

public A(){
this.i = 10;
}

public int get(){
return i;
}
}

然后運行javac A.java 生成A.class此時A.class的位置是F:\tmp

其次我們編寫Unsafe加載class的代碼

/**
   * 通過class文件獲得二進制
   * @return
   * @throws IOException
   */
  public static byte[] loadClassContent() throws IOException {
      File file = new File("F:\\tmp\\a.class");
      FileInputStream fis = new FileInputStream(file);
      byte[] content = new byte[(int) file.length()];
      fis.read(content);
      fis.close();
      return content;
  }
  public static void main(String[] args) throws Exception {
      Unsafe unsafe = getUnsafe();
      byte[] bytes = loadClassContent();
      Class<?> aClass = unsafe.defineClass(null, bytes, 0, bytes.length,null,null);
      Method get = aClass.getMethod("get");
      int i = (int) get.invoke(aClass.newInstance(), null);
      System.out.println(i);
  }

這里unsafe.defineClass方法就是加載類的方法。

運行后輸出結果為10

這樣我們就實現了通過Unsafe加載類。

Unsafe更改私有屬性值

我們都知道反射可以更改對象私有屬性值,其實Unsafe也可以直接更改私有屬性值,代碼如下

static class Guard{
      private int ACCESS_ALLOWED = 1;

      private boolean allow(){
          return 42==ACCESS_ALLOWED;
      }

      public void work(){
          if (allow()){
              System.out.println("你進行了暗箱操作");
          }
      }
}
public static void main(String[] args) throws Exception {
      Unsafe unsafe = getUnsafe();
      Guard guard = new Guard();
      Field access_allowed = guard.getClass().getDeclaredField("ACCESS_ALLOWED");
      unsafe.putInt(guard,unsafe.objectFieldOffset(access_allowed),42);
      guard.work();
}

輸出結果為 你進行了暗箱操作 ,putInt方法第一個參數是要更改的屬性屬于哪個對象,第二個參數是要更改屬性的內存偏移量,第三個參數是要改成什么值。其實就是直接更改指定內存地址中的int屬性的值。這樣我們就完成了使用Unsafe更改對象私有屬性值。

Unsafe類能直接操作內存的特性決定了它能走太多的后門了,而且大部分方法都是native修飾的,底層調用的C++。估計這也是Unsafe的不安全的原因。

總結

到此這篇關于Java Unsafe用法詳解的文章就介紹到這了,更多相關Java Unsafe用法內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!

原文鏈接:https://www.cnblogs.com/jidiqi/p/15481523.html

延伸 · 閱讀

精彩推薦
  • Java教程詳解Spring Cloud Netflix Zuul中的速率限制

    詳解Spring Cloud Netflix Zuul中的速率限制

    這篇文章主要介紹了詳解Spring Cloud Netflix Zuul中的速率限制,小編覺得挺不錯的,現在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧...

    jdon4632021-06-11
  • Java教程Java 結構化數據處理開源庫 SPL

    Java 結構化數據處理開源庫 SPL

    如果我們在Java中也提供有一套完整的結構化數據處理和計算類庫,那這個問題就能得到解決:即享受到架構的優勢,又不致于降低開發效率。...

    Java知音3882021-12-29
  • Java教程SpringMVC互聯網軟件架構REST使用詳解

    SpringMVC互聯網軟件架構REST使用詳解

    這篇文章主要為大家詳細介紹了SpringMVC互聯網軟件架構REST的相關資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下...

    MrSaber2882020-08-26
  • Java教程springBoot整合redis使用案例詳解

    springBoot整合redis使用案例詳解

    這篇文章主要介紹了springBoot整合redis使用案例詳解,本文通過圖文實例相結合給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的...

    伏加特遇上西柚9192022-01-11
  • Java教程Java 數據流之Broadcast State

    Java 數據流之Broadcast State

    這篇文章主要介紹了Java 數據流之Broadcast State,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下...

    Vicky_Tang7702021-12-31
  • Java教程Java自定義標簽用法實例分析

    Java自定義標簽用法實例分析

    這篇文章主要介紹了Java自定義標簽用法,結合實例形式分析了java自定義標簽的定義、使用方法與相關注意事項,需要的朋友可以參考下...

    目盡地平線11542021-02-01
  • Java教程Java數據結構及算法實例:考拉茲猜想 Collatz Conjecture

    Java數據結構及算法實例:考拉茲猜想 Collatz Conjecture

    這篇文章主要介紹了Java數據結構及算法實例:考拉茲猜想 Collatz Conjecture,本文直接給出實現代碼,代碼中包含詳細注釋,需要的朋友可以參考下 ...

    junjie5222019-12-23
  • Java教程如何把Spring Cloud Data Flow部署在Kubernetes上

    如何把Spring Cloud Data Flow部署在Kubernetes上

    這篇文章主要介紹了把Spring Cloud Data Flow部署在Kubernetes上,再跑個任務試試,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考...

    南瓜慢說5592020-08-03
主站蜘蛛池模板: 亚洲精品国产专区91在线 | 国产老肥熟xxxx | 四虎永久在线精品波多野结衣 | 免费观看无人区完整版 | 午夜成私人影院在线观看 | 国产精品久久国产精品99 | 国产精品视频久久久久 | 成人久久伊人精品伊人 | 91在线高清视频 | 日韩欧美不卡视频 | 久久这里只精品热在线18 | 精品国产乱码久久久久久免费流畅 | 无人影院在线播放 | 免费观看欧美成人h | 美女脱了内裤打开腿让人桶网站o | 婷婷色天使在线视频观看 | 波多野结衣中文字幕乱七八糟 | 日本伦理动漫在线观看 | 色老板在线| 欧美高清日韩 | 男女交性特一级 | 春色视频网站 | 亚洲不卡视频在线观看 | 从后面撕开老师的丝袜动态图 | 亚洲视频免费在线看 | 美国雪白人妖sarina | 日本老妇乱子伦中文视频 | 亚洲精品午夜视频 | 三级无删减高清在线影院 | 国产精品青青在线观看香蕉 | 王小军怎么了最新消息 | 热色综合 | 美女自插 | 国产香蕉视频在线观看 | 欧美高清milf在线播放 | 性做久久久久久久久老女人 | 日韩 国产 欧美 | 天海翼最新作品 | 我的漂亮朋友在线观看全集免费 | 明星梦淫 | 国产精品合集久久久久青苹果 |