欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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

 更新時間:2021年10月30日 12:20:12   作者:接地氣程序員  
Unsafe是位于sun.misc包下的一個類,主要提供一些用于執(zhí)行低級別、不安全操作的方法,如直接訪問系統(tǒng)內(nèi)存資源、自主管理內(nèi)存資源等,下面這篇文章主要給大家介紹了關(guān)于Java Unsafe用法的相關(guān)資料,需要的朋友可以參考下

Unsafe是什么

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

如何正確地獲取Unsafe對象

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

private Unsafe() {
}

首先構(gòu)造方法私有化,這就說明我們不能通過new Unsafe的方式創(chuàng)建Unsafe對象。

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

我又發(fā)現(xiàn)有一個靜態(tài)方法,方法名為getUnsafe,而且返回值為Unsafe,看來調(diào)用這個方法可以獲取Unsafe對象。

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

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

誰知道控制臺竟然報錯了

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

繼續(xù)看源碼找突破口

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

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進(jìn)行了賦值。也就是說在類加載完成后Unsafe里面的theUnsafe常量就已經(jīng)賦值好了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;
    }

Unsafe實現(xiàn)CAS鎖

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

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

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

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

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

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

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

使用Unsafe創(chuàng)建對象

我們都知道反射可以‘走后門'創(chuàng)建對象,其實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();
        //相當(dāng)于直接在內(nèi)存中開辟一塊地址,不運行構(gòu)造方法
        Simple simple = (Simple) unsafe.allocateInstance(Simple.class);
        System.out.println("通過unsafe創(chuàng)建對象不會運行構(gòu)造方法: " + simple.get());
        System.out.println("但是可以通過對象獲得class對象" + simple.getClass());
        System.out.println("也可以拿到類加載器 " + simple.getClass().getClassLoader());
 }

控制臺輸出如下

這里我們發(fā)現(xiàn)使用Unsafe創(chuàng)建對象并沒有運行構(gòu)造方法,而只是將對象創(chuàng)建出來了。而使用反射創(chuàng)建對象是會運行構(gòu)造方法的和使用new的方式創(chuàng)建對象別無二致。所以不推薦使用Unsafe創(chuàng)建對象。

Unsafe加載類

既然Unsafe是直接操作的內(nèi)存那應(yīng)該也可以加載類,下面我們看看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文件獲得二進(jìn)制
     * @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方法就是加載類的方法。

運行后輸出結(jié)果為10

這樣我們就實現(xiàn)了通過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("你進(jìn)行了暗箱操作");
            }
        }
 }
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();
}

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

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

總結(jié)

到此這篇關(guān)于Java Unsafe用法詳解的文章就介紹到這了,更多相關(guān)Java Unsafe用法內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Spring Boot項目利用Redis實現(xiàn)session管理實例

    Spring Boot項目利用Redis實現(xiàn)session管理實例

    本篇文章主要介紹了Spring Boot項目利用Redis實現(xiàn)session管理實例,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-06-06
  • IDEA一鍵生成方法的序列圖神操作

    IDEA一鍵生成方法的序列圖神操作

    為了能夠更快更清晰地搞清對象之間的調(diào)用關(guān)系,我經(jīng)常需要用到序列圖。手動畫序列圖還是很麻煩費時的,不過?IDEA?提供了一個叫做SequenceDiagram?的插件幫助我們解決這個問題。通SequenceDiagram?這個插件,我們一鍵可以生成時序圖
    2022-01-01
  • Java注解處理器簡單實例

    Java注解處理器簡單實例

    這篇文章主要介紹了Java注解處理器簡單實例,具有一定借鑒價值,需要的朋友可以參考下
    2018-01-01
  • Spring Cloud Eureka 服務(wù)上下線監(jiān)控的實現(xiàn)

    Spring Cloud Eureka 服務(wù)上下線監(jiān)控的實現(xiàn)

    這篇文章主要介紹了Spring Cloud Eureka 服務(wù)上下線監(jiān)控的實現(xiàn),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-09-09
  • mybatis實現(xiàn)遍歷Map的key和value

    mybatis實現(xiàn)遍歷Map的key和value

    這篇文章主要介紹了mybatis實現(xiàn)遍歷Map的key和value方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-01-01
  • Java內(nèi)存模型知識詳解

    Java內(nèi)存模型知識詳解

    這篇文章主要介紹了Java內(nèi)存模型知識詳解,文中通過對內(nèi)存訪問時的交互關(guān)系圖解介紹的十分詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-07-07
  • 利用Java實現(xiàn)mTLS調(diào)用

    利用Java實現(xiàn)mTLS調(diào)用

    這篇文章主要介紹使用 Java作為客戶端 與受 mTLS 保護(hù)的服務(wù)交互。為了對我們的 Java 客戶端進(jìn)行 ssl 配置,我們需要先設(shè)置一個 SSLContext。這簡化了事情,因為 SSLContext 可用于各種 http 客戶端,接下來我們一起進(jìn)入下面文章了解具體內(nèi)容,需要的朋友可以參考一下
    2021-11-11
  • Java使用JavaMail API發(fā)送和接收郵件的代碼示例

    Java使用JavaMail API發(fā)送和接收郵件的代碼示例

    JavaMail是Oracle甲骨文開發(fā)的Java郵件類API,支持多種郵件協(xié)議,這里我們就來看一下Java使用JavaMail API發(fā)送和接收郵件的代碼示例
    2016-06-06
  • java?中的HashMap的底層實現(xiàn)和元素添加流程

    java?中的HashMap的底層實現(xiàn)和元素添加流程

    這篇文章主要介紹了java?中的HashMap的底層實現(xiàn)和元素添加流程,HashMap?是使用頻率最高的數(shù)據(jù)類型之一,同時也是面試必問的問題之一,尤其是它的底層實現(xiàn)原理,下文更多詳細(xì)內(nèi)容,需要的小伙伴可以參考一下
    2022-05-05
  • 利用maven deploy上傳本地jar至私服的方法

    利用maven deploy上傳本地jar至私服的方法

    這篇文章主要介紹了利用maven deploy上傳本地jar至私服的方法,本文結(jié)合實例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2023-02-02

最新評論