java Unsafe詳細(xì)解析
問(wèn)題
(1)Unsafe是什么?
(2)Unsafe只有CAS的功能嗎?
(3)Unsafe為什么是不安全的?
(4)怎么使用Unsafe?
簡(jiǎn)介
Unsafe為我們提供了訪(fǎng)問(wèn)底層的機(jī)制,這種機(jī)制僅供java核心類(lèi)庫(kù)使用,而不應(yīng)該被普通用戶(hù)使用。
但是,為了更好地了解java的生態(tài)體系,我們應(yīng)該去學(xué)習(xí)它,去了解它,不求深入到底層的C/C++代碼,但求能了解它的基本功能。
獲取Unsafe的實(shí)例
查看Unsafe的源碼我們會(huì)發(fā)現(xiàn)它提供了一個(gè)getUnsafe()的靜態(tài)方法。
@CallerSensitive
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
但是,如果直接調(diào)用這個(gè)方法會(huì)拋出一個(gè)SecurityException異常,這是因?yàn)閁nsafe僅供java內(nèi)部類(lèi)使用,外部類(lèi)不應(yīng)該使用它。
那么,我們就沒(méi)有方法了嗎?
當(dāng)然不是,我們有反射??!查看源碼,我們發(fā)現(xiàn)它有一個(gè)屬性叫theUnsafe,我們直接通過(guò)反射拿到它即可。
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實(shí)例化一個(gè)類(lèi)
假如我們有一個(gè)簡(jiǎn)單的類(lèi)如下:
class User {
int age;
public User() {
this.age = 10;
}
}
如果我們通過(guò)構(gòu)造方法實(shí)例化這個(gè)類(lèi),age屬性將會(huì)返回10。
User user1 = new User(); // 打印10 System.out.println(user1.age);
如果我們調(diào)用Unsafe來(lái)實(shí)例化呢?
User user2 = (User) unsafe.allocateInstance(User.class); // 打印0 System.out.println(user2.age);
age將返回0,因?yàn)閁nsafe.allocateInstance()只會(huì)給對(duì)象分配內(nèi)存,并不會(huì)調(diào)用構(gòu)造方法,所以這里只會(huì)返回int類(lèi)型的默認(rèn)值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;
}
}
一旦我們通過(guò)反射調(diào)用得到字段age,我們就可以使用Unsafe將其值更改為任何其他int值。(當(dāng)然,這里也可以通過(guò)反射直接修改)
拋出checked異常
我們知道如果代碼拋出了checked異常,要不就使用try...catch捕獲它,要不就在方法簽名上定義這個(gè)異常,但是,通過(guò)Unsafe我們可以?huà)伋鲆粋€(gè)checked異常,同時(shí)卻不用捕獲或在方法簽名上定義它。
// 使用正常方式拋出IOException需要定義在方法簽名上往外拋
public static void readFile() throws IOException {
throw new IOException();
}
// 使用Unsafe拋出異常不需要定義在方法簽名上往外拋
public static void readFileUnsafe() {
unsafe.throwException(new IOException());
}
使用堆外內(nèi)存
如果進(jìn)程在運(yùn)行過(guò)程中JVM上的內(nèi)存不足了,會(huì)導(dǎo)致頻繁的進(jìn)行GC。理想情況下,我們可以考慮使用堆外內(nèi)存,這是一塊不受JVM管理的內(nèi)存。
使用Unsafe的allocateMemory()我們可以直接在堆外分配內(nèi)存,這可能非常有用,但我們要記住,這個(gè)內(nèi)存不受JVM管理,因此我們要調(diào)用freeMemory()方法手動(dòng)釋放它。
假設(shè)我們要在堆外創(chuàng)建一個(gè)巨大的int數(shù)組,我們可以使用allocateMemory()方法來(lái)實(shí)現(xiàn):
class OffHeapArray {
// 一個(gè)int等于4個(gè)字節(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();
}
}
// 構(gòu)造方法,分配內(nèi)存
public OffHeapArray(long size) {
this.size = size;
// 參數(shù)字節(jié)數(shù)
address = unsafe.allocateMemory(size * INT);
}
// 獲取指定索引處的元素
public int get(long i) {
return unsafe.getInt(address + i * INT);
}
// 設(shè)置指定索引處的元素
public void set(long i, int value) {
unsafe.putInt(address + i * INT, value);
}
// 元素個(gè)數(shù)
public long size() {
return size;
}
// 釋放堆外內(nèi)存
public void freeMemory() {
unsafe.freeMemory(address);
}
}
在構(gòu)造方法中調(diào)用allocateMemory()分配內(nèi)存,在使用完成后調(diào)用freeMemory()釋放內(nèi)存。
使用方式如下:
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的位置重復(fù)放入元素
int sum = 0;
for (int i = 0; i < offHeapArray.size(); i++) {
sum += offHeapArray.get(i);
}
// 打印12
System.out.println(sum);
offHeapArray.freeMemory();
最后,一定要記得調(diào)用freeMemory()將內(nèi)存釋放回操作系統(tǒng)。
CompareAndSwap操作
JUC下面大量使用了CAS操作,它們的底層是調(diào)用的Unsafe的CompareAndSwapXXX()方法。這種方式廣泛運(yùn)用于無(wú)鎖算法,與java中標(biāo)準(zhǔn)的悲觀鎖機(jī)制相比,它可以利用CAS處理器指令提供極大的加速。
比如,我們可以基于Unsafe的compareAndSwapInt()方法構(gòu)建線(xiàn)程安全的計(jì)數(shù)器。
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;
}
}
我們定義了一個(gè)volatile的字段count,以便對(duì)它的修改所有線(xiàn)程都可見(jiàn),并在類(lèi)加載的時(shí)候獲取count在類(lèi)中的偏移地址。
在increment()方法中,我們通過(guò)調(diào)用Unsafe的compareAndSwapInt()方法來(lái)嘗試更新之前獲取到的count的值,如果它沒(méi)有被其它線(xiàn)程更新過(guò),則更新成功,否則不斷重試直到成功為止。
我們可以通過(guò)使用多個(gè)線(xiàn)程來(lái)測(cè)試我們的代碼:
Counter counter = new Counter(); ExecutorService threadPool = Executors.newFixedThreadPool(100); // 起100個(gè)線(xiàn)程,每個(gè)線(xiàn)程自增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/unparkJVM
在上下文切換的時(shí)候使用了Unsafe中的兩個(gè)非常牛逼的方法park()和unpark()。
當(dāng)一個(gè)線(xiàn)程正在等待某個(gè)操作時(shí),JVM調(diào)用Unsafe的park()方法來(lái)阻塞此線(xiàn)程。
當(dāng)阻塞中的線(xiàn)程需要再次運(yùn)行時(shí),JVM調(diào)用Unsafe的unpark()方法來(lái)喚醒此線(xiàn)程。
我們之前在分析java中的集合時(shí)看到了大量的LockSupport.park()/unpark(),它們底層都是調(diào)用的Unsafe的這兩個(gè)方法。
總結(jié)
使用Unsafe幾乎可以操作一切:
(1)實(shí)例化一個(gè)類(lèi);
(2)修改私有字段的值;
(3)拋出checked異常;
(4)使用堆外內(nèi)存;
(5)CAS操作;
(6)阻塞/喚醒線(xiàn)程;
彩蛋
論實(shí)例化一個(gè)類(lèi)的方式?
(1)通過(guò)構(gòu)造方法實(shí)例化一個(gè)類(lèi);
(2)通過(guò)Class實(shí)例化一個(gè)類(lèi);
(3)通過(guò)反射實(shí)例化一個(gè)類(lèi);
(4)通過(guò)克隆實(shí)例化一個(gè)類(lèi);
(5)通過(guò)反序列化實(shí)例化一個(gè)類(lèi);
(6)通過(guò)Unsafe實(shí)例化一個(gè)類(lèi);
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. 構(gòu)造方法
User user1 = new User();
// 2. Class,里面實(shí)際也是反射
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();
}
}
}
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
手把手教你使用IDEA創(chuàng)建多模塊(maven)項(xiàng)目
這篇文章主要給大家介紹了關(guān)于如何使用IDEA創(chuàng)建多模塊(maven)項(xiàng)目的相關(guān)資料,文中通過(guò)圖文以及實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-07-07
繼承jpa?Repository?寫(xiě)自定義方法查詢(xún)實(shí)例
這篇文章主要介紹了繼承jpa?Repository?寫(xiě)自定義方法查詢(xún)實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12
SpringBoot+layuimini實(shí)現(xiàn)左側(cè)菜單動(dòng)態(tài)展示的示例代碼
Layuimini是Layui的升級(jí)版,它是專(zhuān)業(yè)做后臺(tái)頁(yè)面的框架,而且是適合PC端和移動(dòng)端,以下地址可以在PC端顯示,也可以在手機(jī)上顯示,只不過(guò)會(huì)做自適應(yīng),本文將給大家介紹了SpringBoot+layuimini實(shí)現(xiàn)左側(cè)菜單動(dòng)態(tài)展示的方法,需要的朋友可以參考下2024-04-04
Springboot?application.yml配置文件拆分方式
這篇文章主要介紹了Springboot?application.yml配置文件拆分方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-05-05

