死磕 java魔法類之Unsafe解析

問題

(1)Unsafe是什么?

(2)Unsafe只有CAS的功能嗎?

(3)Unsafe為什么是不安全的?

(4)怎么使用Unsafe?

簡介

本章是java并發(fā)包專題的第一章,但是第一篇寫的卻不是java并發(fā)包中類,而是java中的魔法類sun.misc.Unsafe。

Unsafe為我們提供了訪問底層的機制,這種機制僅供java核心類庫使用,而不應該被普通用戶使用。

但是,為了更好地了解java的生態(tài)體系,我們應該去學習它,去了解它,不求深入到底層的C/C++代碼,但求能了解它的基本功能。

獲取Unsafe的實例

查看Unsafe的源碼我們會發(fā)現它提供了一個getUnsafe()的靜態(tài)方法。

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

但是,如果直接調用這個方法會拋出一個SecurityException異常,這是因為Unsafe僅供java內部類使用,外部類不應該使用它。

那么,我們就沒有方法了嗎?

當然不是,我們有反射??!查看源碼,我們發(fā)現它有一個屬性叫theUnsafe,我們直接通過反射拿到它即可。

public class UnsafeTest { public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException { Field f = Unsafe.class.getDeclaredField("theUnsafe"); f.setAccessible(true); Unsafe unsafe = (Unsafe) f.get(null); } }

使用Unsafe實例化一個類

假如我們有一個簡單的類如下:

class User { int age; public User() { this.age = 10; } }

如果我們通過構造方法實例化這個類,age屬性將會返回10

User user1 = new User(); // 打印10 System.out.println(user1.age);

如果我們調用Unsafe來實例化呢?

User user2 = (User) unsafe.allocateInstance(User.class); // 打印0 System.out.println(user2.age);

age將返回0,因為Unsafe.allocateInstance()只會給對象分配內存,并不會調用構造方法,所以這里只會返回int類型的默認值0。

修改私有字段的值

使用Unsafe的putXXX()方法,我們可以修改任意私有字段的值。

public class UnsafeTest { public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InstantiationException { Field f = Unsafe.class.getDeclaredField("theUnsafe"); f.setAccessible(true); Unsafe unsafe = (Unsafe) f.get(null); User user = new User(); Field age = user.getClass().getDeclaredField("age"); unsafe.putInt(user, unsafe.objectFieldOffset(age), 20); // 打印20 System.out.println(user.getAge()); } } class User { private int age; public User() { this.age = 10; } public int getAge() { return age; } }

一旦我們通過反射調用得到字段age,我們就可以使用Unsafe將其值更改為任何其他int值。(當然,這里也可以通過反射直接修改)

拋出checked異常

我們知道如果代碼拋出了checked異常,要不就使用try...catch捕獲它,要不就在方法簽名上定義這個異常,但是,通過Unsafe我們可以拋出一個checked異常,同時卻不用捕獲或在方法簽名上定義它。

// 使用正常方式拋出IOException需要定義在方法簽名上往外拋 public static void readFile() throws IOException { throw new IOException(); } // 使用Unsafe拋出異常不需要定義在方法簽名上往外拋 public static void readFileUnsafe() { unsafe.throwException(new IOException()); }

使用堆外內存

如果進程在運行過程中JVM上的內存不足了,會導致頻繁的進行GC。理想情況下,我們可以考慮使用堆外內存,這是一塊不受JVM管理的內存。

使用Unsafe的allocateMemory()我們可以直接在堆外分配內存,這可能非常有用,但我們要記住,這個內存不受JVM管理,因此我們要調用freeMemory()方法手動釋放它。

假設我們要在堆外創(chuàng)建一個巨大的int數組,我們可以使用allocateMemory()方法來實現:

class OffHeapArray { // 一個int等于4個字節(jié) private static final int INT = 4; private long size; private long address; private static Unsafe unsafe; static { try { Field f = Unsafe.class.getDeclaredField("theUnsafe"); f.setAccessible(true); unsafe = (Unsafe) f.get(null); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } // 構造方法,分配內存 public OffHeapArray(long size) { this.size = size; // 參數字節(jié)數 address = unsafe.allocateMemory(size * INT); } // 獲取指定索引處的元素 public int get(long i) { return unsafe.getInt(address + i * INT); } // 設置指定索引處的元素 public void set(long i, int value) { unsafe.putInt(address + i * INT, value); } // 元素個數 public long size() { return size; } // 釋放堆外內存 public void freeMemory() { unsafe.freeMemory(address); } }

在構造方法中調用allocateMemory()分配內存,在使用完成后調用freeMemory()釋放內存。

使用方式如下:

OffHeapArray offHeapArray = new OffHeapArray(4); offHeapArray.set(0, 1); offHeapArray.set(1, 2); offHeapArray.set(2, 3); offHeapArray.set(3, 4); offHeapArray.set(2, 5); // 在索引2的位置重復放入元素 int sum = 0; for (int i = 0; i < offHeapArray.size(); i++) { sum += offHeapArray.get(i); } // 打印12 System.out.println(sum); offHeapArray.freeMemory();

最后,一定要記得調用freeMemory()將內存釋放回操作系統(tǒng)。

CompareAndSwap操作

JUC下面大量使用了CAS操作,它們的底層是調用的Unsafe的CompareAndSwapXXX()方法。這種方式廣泛運用于無鎖算法,與java中標準的悲觀鎖機制相比,它可以利用CAS處理器指令提供極大的加速。

比如,我們可以基于Unsafe的compareAndSwapInt()方法構建線程安全的計數器。

class Counter { private volatile int count = 0; private static long offset; private static Unsafe unsafe; static { try { Field f = Unsafe.class.getDeclaredField("theUnsafe"); f.setAccessible(true); unsafe = (Unsafe) f.get(null); offset = unsafe.objectFieldOffset(Counter.class.getDeclaredField("count")); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } public void increment() { int before = count; // 失敗了就重試直到成功為止 while (!unsafe.compareAndSwapInt(this, offset, before, before + 1)) { before = count; } } public int getCount() { return count; } }

我們定義了一個volatile的字段count,以便對它的修改所有線程都可見,并在類加載的時候獲取count在類中的偏移地址。

在increment()方法中,我們通過調用Unsafe的compareAndSwapInt()方法來嘗試更新之前獲取到的count的值,如果它沒有被其它線程更新過,則更新成功,否則不斷重試直到成功為止。

我們可以通過使用多個線程來測試我們的代碼:

Counter counter = new Counter(); ExecutorService threadPool = Executors.newFixedThreadPool(100); // 起100個線程,每個線程自增10000次 IntStream.range(0, 100) .forEach(i->threadPool.submit(()->IntStream.range(0, 10000) .forEach(j->counter.increment()))); threadPool.shutdown(); Thread.sleep(2000); // 打印1000000 System.out.println(counter.getCount());

park/unpark

JVM在上下文切換的時候使用了Unsafe中的兩個非常牛逼的方法park()和unpark()。

當一個線程正在等待某個操作時,JVM調用Unsafe的park()方法來阻塞此線程。

當阻塞中的線程需要再次運行時,JVM調用Unsafe的unpark()方法來喚醒此線程。

我們之前在分析java中的集合時看到了大量的LockSupport.park()/unpark(),它們底層都是調用的Unsafe的這兩個方法。

總結

使用Unsafe幾乎可以操作一切:

(1)實例化一個類;

(2)修改私有字段的值;

(3)拋出checked異常;

(4)使用堆外內存;

(5)CAS操作;

(6)阻塞/喚醒線程;

彩蛋

論實例化一個類的方式?

(1)通過構造方法實例化一個類;

(2)通過Class實例化一個類;

(3)通過反射實例化一個類;

(4)通過克隆實例化一個類;

(5)通過反序列化實例化一個類;

(6)通過Unsafe實例化一個類;

public class InstantialTest { private static Unsafe unsafe; static { try { Field f = Unsafe.class.getDeclaredField("theUnsafe"); f.setAccessible(true); unsafe = (Unsafe) f.get(null); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } public static void main(String[] args) throws Exception { // 1. 構造方法 User user1 = new User(); // 2. Class,里面實際也是反射 User user2 = User.class.newInstance(); // 3. 反射 User user3 = User.class.getConstructor().newInstance(); // 4. 克隆 User user4 = (User) user1.clone(); // 5. 反序列化 User user5 = unserialize(user1); // 6. Unsafe User user6 = (User) unsafe.allocateInstance(User.class); System.out.println(user1.age); System.out.println(user2.age); System.out.println(user3.age); System.out.println(user4.age); System.out.println(user5.age); System.out.println(user6.age); } private static User unserialize(User user1) throws Exception { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D://object.txt")); oos.writeObject(user1); oos.close(); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D://object.txt")); // 反序列化 User user5 = (User) ois.readObject(); ois.close(); return user5; } static class User implements Cloneable, Serializable { private int age; public User() { this.age = 10; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } } }

加java高級架構師群獲取Java工程化、高性能及分布式、高性能、深入淺出。高架構。

性能調優(yōu)、Spring,MyBatis,Netty源碼分析和大數據等多個知識點高級進階干貨的直播免費學習權限

都是大牛帶飛?讓你少走很多的彎路的?群號是:?798891710最好是有開發(fā)經驗

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容