Unsafe類的作用
Unsafe類提供了硬件級別的原子操作,Java無法直接訪問到操作系統(tǒng)底層(如系統(tǒng)硬件等),為此Java使用native方法來擴展Java程序的功能。Java并發(fā)包(java.util.concurrent)中大量使用了Unsafe類提供的CAS方法。Unsafe類還提供了阻塞和喚醒線程的方法,以及volatile read/write操作等。
獲取Unsafe對象
public final class Unsafe {
private static final Unsafe theUnsafe;
// ...
private Unsafe() {
}
@CallerSensitive
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
// ...
}
Unsafe被設(shè)計成單例模式,構(gòu)造方法是私有的,不能直接通過new Unsafe();
-
不能通過調(diào)用Unsafe.getUnsafe()獲取,因為getUnsafe被設(shè)計成只能從引導類加載器(bootstrap class loader)加載
getClassLoader鏈接:https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html#getClassLoader--
正確獲取Unsafe的姿勢
package com.sankuai.meituan;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
public class UnsafeWrapper {
private static Unsafe unsafe;
public static Unsafe getUnsafe() {
if (unsafe == null) {
try {
// 通過反射得到unsafe對應(yīng)的Field對象
Field field = Unsafe.class.getDeclaredField("theUnsafe");
// 設(shè)置該Field為可訪問
field.setAccessible(true);
// 通過Field得到該Field對應(yīng)的具體對象,傳入null是因為該Field為static的
unsafe = (Unsafe) field.get(null);
} catch (Exception e) {
e.printStackTrace();
}
}
return unsafe;
}
}
Unsafe類中的API
1.內(nèi)存管理,直接操作內(nèi)存的方法
//分配指定大小的內(nèi)存
public native long allocateMemory(long bytes);
//重新分配 (擴展) 一塊內(nèi)存 (之前分配內(nèi)存當然會被GC回收掉). 第一個參數(shù)為原內(nèi)存地址,返回新的內(nèi)存地址. 原內(nèi)存中的內(nèi)容會遷移到新開辟的內(nèi)存中.
public native long reallocateMemory(long address, long bytes);
//用于釋放allocateMemory和reallocateMemory申請的內(nèi)存
public native void freeMemory(long address);
//設(shè)置給定內(nèi)存地址的值
public native void putAddress(long address, long x);
//獲取指定內(nèi)存地址的值
public native long getAddress(long address);
//設(shè)置指定內(nèi)存的byte值
public native byte getByte(long address);
//設(shè)置指定內(nèi)存的byte值
public native void putByte(long address, byte x);
//其他基本數(shù)據(jù)類型(long,char,float,double,short等)的操作與putByte及getByte相同
package com.sankuai.meituan;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import sun.misc.Unsafe;
@Slf4j
public class UnsafeMemoryTest {
@Test
public void test() {
Unsafe unsafe = UnsafeWrapper.getUnsafe();
int size = 16;
int data = 1024;
long memoryAddress = unsafe.allocateMemory(size);
//直接往內(nèi)存寫入數(shù)據(jù)
unsafe.putAddress(memoryAddress, data);
//獲取指定內(nèi)存地址的數(shù)據(jù)
log.info("getAddress:{}, data:{}", unsafe.getAddress(memoryAddress), data);
//putLong和putAddress效果一樣
data = 2 * data;
unsafe.putLong(memoryAddress, data);
unsafe.putAddress(memoryAddress + 8, data * 2);
unsafe.putAddress(memoryAddress + 16, data * 4);
unsafe.putAddress(memoryAddress + 24, data * 8);
log.info("getAddress:{}, data:{}", unsafe.getAddress(memoryAddress), data);
//調(diào)換順序報錯
long reallocateMemoryAddress = unsafe.reallocateMemory(memoryAddress, size * 1024);
log.info("memoryAddress:{}, reallocateMemoryAddress:{}", memoryAddress, reallocateMemoryAddress);
log.info("getAddress:{}, data:{}", unsafe.getAddress(reallocateMemoryAddress), data);
log.info("getAddress+8:{}, data:{}", unsafe.getAddress(reallocateMemoryAddress + 8), data * 2);
log.info("getAddress+16:{}, data:{}", unsafe.getAddress(reallocateMemoryAddress + 16), data * 4);
log.info("getAddress+24:{}, data:{}", unsafe.getAddress(reallocateMemoryAddress + 24), data * 8);
log.info("getAddress+32:{}, data:{}", unsafe.getAddress(reallocateMemoryAddress + 32), data * 16);
unsafe.freeMemory(reallocateMemoryAddress);
}
}
在不同的jdk版本上allocateMemory申請空間的時候會在頭尾加上一點緩沖區(qū),比如這里的size是24,分配的是32(最小大小是16,就是size小于等于16,分配的是16)。
2.動態(tài)類加載操作
//告訴JVM定義一個類,返回類實例,此方法會跳過JVM的所有安全檢查。
public native Class defineClass(String name, byte[] b, int off, int len, ClassLoader loader, ProtectionDomain protectionDomain);
//加載一個匿名類
public native Class defineAnonymousClass(Class hostClass, byte[] data, Object[] cpPatches);
//判斷是否需要加載一個類
public native boolean shouldBeInitialized(Class<?> c);
//確保類一定被加載
public native void ensureClassInitialized(Class<?> c)
package com.sankuai.meituan;
import org.junit.Test;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class UnsafeDefineClassTest {
@Test
public void test() throws IOException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
File f = new File("/Users/jec/IdeaProjects/UnsafeLearning/src/test/java/com/sankuai/meituan/Hello.class");
FileInputStream input = new FileInputStream(f);
byte[] content = new byte[(int) f.length()];
input.read(content);
input.close();
Class c = UnsafeWrapper.getUnsafe().defineClass(null, content, 0, content.length, null, null);
Method m = c.getMethod("hello");
m.invoke(c.newInstance(), null);
}
}
3.非常規(guī)實例化對象
//傳入一個對象的class并創(chuàng)建該實例對象,但不會調(diào)用構(gòu)造方法
public native Object allocateInstance(Class cls) throws InstantiationException;
package com.sankuai.meituan;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import sun.misc.Unsafe;
@Slf4j
public class UnsafeAllocateInstanceTest {
@Test
public void test() throws InstantiationException {
Unsafe unsafe = UnsafeWrapper.getUnsafe();
Rectangle rectangle = (Rectangle) unsafe.allocateInstance(Rectangle.class);
log.info("func=unsafeAllocateInstanceTest rectangle:{}", rectangle);
}
}
4.實例對象字段以及靜態(tài)屬性的操作
//獲取字段f在實例對象中的偏移量
public native long objectFieldOffset(Field f);
//靜態(tài)屬性的偏移量,用于在對應(yīng)的Class對象中讀寫靜態(tài)屬性
public native long staticFieldOffset(Field f);
//返回值就是f.getDeclaringClass()
public native Object staticFieldBase(Field f);
//獲得給定對象偏移量上的int值,所謂的偏移量可以簡單理解為指針指向該變量的內(nèi)存地址,
//通過偏移量便可得到該對象的變量,進行各種操作
public native int getInt(Object o, long offset);
//設(shè)置給定對象上偏移量的int值
public native void putInt(Object o, long offset, int x);
//設(shè)置給定對象的int值,使用volatile語義,即設(shè)置后立馬更新到內(nèi)存對其他線程可見
public native void putIntVolatile(Object o, long offset, int x);
//獲得給定對象的指定偏移量offset的int值,使用volatile語義,能獲取到最新的int值。
public native int getIntVolatile(Object o, long offset);
//這是一個有延遲的方法,使用這個方法寫入volatile對象只能夠避免寫重排序,但使volatile失去可見性,不保證值的改變被其他線程立即看到,這樣的技巧可以在某些場景下提高效率。
public native void putOrderedInt(Object o,long offset,int x);
//其他基本數(shù)據(jù)類型(long,char,byte,float,double)的操作與getInt、putInt、putIntVolatile、getIntVolatile、putOrderedInt相同,引用類型Object也一樣。
//將指定對象的給定offset偏移量內(nèi)存塊中的所有字節(jié)設(shè)置為固定值
public native void setMemory(Object o, long offset, long bytes, byte value);
//將給定offset偏移量內(nèi)存塊中的所有字節(jié)bytes設(shè)置為固定值value
public void setMemory(long offset, long bytes, byte value);
//從srcObj對象的srcOffset位置開始復制bytes個字節(jié)到destObj(從destOffset開始)
public native void copyMemory(Object srcObj, long srcOffset, Object destObj, long destOffset, long bytes);
//從srcOffset復制bytes個字節(jié)到destOffset開始的內(nèi)存塊
public void copyMemory(long srcOffset, long destOffset, long bytes)
package com.sankuai.meituan;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import sun.misc.Unsafe;
@Slf4j
public class UnsafeObjectTest {
@Test
public void test() throws NoSuchFieldException {
Unsafe unsafe = UnsafeWrapper.getUnsafe();
int h = 5, w = 6;
Rectangle rectangle = new Rectangle();
//獲取對象字段的偏移位置
long hObjectFieldOffset = unsafe.objectFieldOffset(Rectangle.class.getDeclaredField("h"));
long wObjectFieldOffset = unsafe.objectFieldOffset(Rectangle.class.getDeclaredField("w"));
log.info("hObjectFieldOffset:{}, wObjectFieldOffset:{}", hObjectFieldOffset, wObjectFieldOffset);
//設(shè)置對象上的字段值
unsafe.putOrderedInt(rectangle, hObjectFieldOffset, h);
unsafe.putInt(rectangle, wObjectFieldOffset, w);
//獲取對象上的字段值
log.info("get rectangle.h:{}, h:{}", unsafe.getInt(rectangle, hObjectFieldOffset), h);
log.info("get rectangle.w:{}, h:{}", unsafe.getInt(rectangle, wObjectFieldOffset), w);
Rectangle subRectangle = new Rectangle(h, w, null);
long subRectangleObjectFieldOffset = unsafe.objectFieldOffset(Rectangle.class.getDeclaredField("subRectangle"));
log.info("subRectangleObjectFieldOffset:{}", subRectangleObjectFieldOffset);//設(shè)置對象上的字段值
unsafe.putObject(rectangle, subRectangleObjectFieldOffset, subRectangle);
//獲取對象上的字段值
log.info("get subRectangle:{}", unsafe.getObject(rectangle, subRectangleObjectFieldOffset));
int count = 4;
//獲取靜態(tài)字段的偏移位置
log.info("staticFieldBase count:{} ", unsafe.staticFieldBase(Rectangle.class.getDeclaredField("count")));
long staticFieldOffset = unsafe.staticFieldOffset(Rectangle.class.getDeclaredField("o"));
log.info("staticFieldOffset:{}", staticFieldOffset);
//設(shè)置類靜態(tài)字段得值
unsafe.putInt(Rectangle.class, staticFieldOffset, count);
//獲取類靜態(tài)字段得值
log.info("get Rectangle.count:{}, count:{}", unsafe.getInt(Rectangle.class, staticFieldOffset), count);
unsafe.setMemory(rectangle, hObjectFieldOffset, 8, (byte)0);
log.info("get rectangle.h:{}, get rectangle.w:{}", unsafe.getInt(rectangle, hObjectFieldOffset), unsafe.getInt(rectangle, wObjectFieldOffset));
}
}
5.數(shù)組操作
//獲取數(shù)組第一個元素的偏移地址
public native int arrayBaseOffset(Class arrayClass);
//數(shù)組中一個元素占據(jù)的內(nèi)存空間,arrayBaseOffset與arrayIndexScale配合使用,可定位數(shù)組中每個元素在內(nèi)存中的位置
public native int arrayIndexScale(Class arrayClass);
package com.sankuai.meituan;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import sun.misc.Unsafe;
@Slf4j
public class UnsafeArrayTest {
@Test
public void test() {
Unsafe unsafe = UnsafeWrapper.getUnsafe();
Rectangle rectangle = new Rectangle(5, 6, null);
Object[] array = new Object[] {rectangle, rectangle, rectangle};
//獲取數(shù)組第一個元素的偏移地址
long baseOffset = unsafe.arrayBaseOffset(Object[].class);
//獲取JVM的地址空間大小
int addressSize = unsafe.addressSize();
//獲取對象地址
long objectAddress;
switch (addressSize)
{
case 4://32位jvm
objectAddress = unsafe.getInt(array, baseOffset);
break;
case 8://64位jvm
objectAddress = unsafe.getLong(array, baseOffset);
break;
default:
throw new Error("unsupported address size: " + addressSize);
}
log.info("array baseOffset:{}, addressSize:{}, objectAddress:{}", baseOffset, addressSize, objectAddress);
log.info("");
log.info("---------------------------");
log.info("boolean[].class arrayBaseOffset:{}", unsafe.arrayBaseOffset(boolean[].class));
log.info("byte[].class arrayBaseOffset:{}", unsafe.arrayBaseOffset(byte[].class));
log.info("short[].class arrayBaseOffset:{}", unsafe.arrayBaseOffset(short[].class));
log.info("char[].class arrayBaseOffset:{}", unsafe.arrayBaseOffset(char[].class));
log.info("int[].class arrayBaseOffset:{}", unsafe.arrayBaseOffset(int[].class));
log.info("long[].class arrayBaseOffset:{}", unsafe.arrayBaseOffset(long[].class));
log.info("float[].class arrayBaseOffset:{}", unsafe.arrayBaseOffset(float[].class));
log.info("double[].class arrayBaseOffset:{}", unsafe.arrayBaseOffset(double[].class));
log.info("Object[].class arrayBaseOffset:{}", unsafe.arrayBaseOffset(Object[].class));
log.info("boolean[].class arrayIndexScale:{}", unsafe.arrayIndexScale(boolean[].class));
log.info("byte[].class arrayIndexScale:{}", unsafe.arrayIndexScale(byte[].class));
log.info("short[].class arrayIndexScale:{}", unsafe.arrayIndexScale(short[].class));
log.info("char[].class arrayIndexScale:{}", unsafe.arrayIndexScale(char[].class));
log.info("int[].class arrayIndexScale:{}", unsafe.arrayIndexScale(int[].class));
log.info("long[].class arrayIndexScale:{}", unsafe.arrayIndexScale(long[].class));
log.info("float[].class arrayIndexScale:{}", unsafe.arrayIndexScale(float[].class));
log.info("double[].class arrayIndexScale:{}", unsafe.arrayIndexScale(double[].class));
log.info("Object[].class arrayIndexScale:{}", unsafe.arrayIndexScale(Object[].class));
}
}
6.多線程同步
//獲取持有鎖,已不建議使用
@Deprecated
public native void monitorEnter(Object o);
//釋放鎖,已不建議使用
@Deprecated
public native void monitorExit(Object o);
//嘗試獲取鎖,已不建議使用
@Deprecated
public native boolean tryMonitorEnter(Object o);
7.CAS 操作相關(guān)
//第一個參數(shù)o為給定對象,offset為對象內(nèi)存的偏移量,通過這個偏移量迅速定位字段并設(shè)置或獲取該字段的值,
//expected表示期望值,x表示要設(shè)置的值,下面3個方法都通過CAS原子指令執(zhí)行操作。
public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object x);
public final native boolean compareAndSwapInt(Object o, long offset,int expected,int x);
public final native boolean compareAndSwapLong(Object o, long offset,long expected,long x);
//以及jdk1.8基于cas操作新增擴展出來的getAndAddInt、getAndAddLong、getAndSetInt、getAndSetLong、getAndSetObject
public final int getAndAddInt(Object o, long offset, int delta)
public final long getAndAddLong(Object o, long offset, long delta)
public final int getAndSetInt(Object o, long offset, int newValue)
public final long getAndSetLong(Object o, long offset, long newValue)
public final Object getAndSetObject(Object o, long offset, Object newValue)
8.掛起與恢復
將一個線程進行掛起是通過park方法實現(xiàn)的,調(diào)用 park后,線程將一直阻塞直到超時或者中斷等條件出現(xiàn)。unpark可以終止一個掛起的線程,使其恢復正常。Java對線程的掛起操作被封裝在 LockSupport類中,LockSupport類中有各種版本pack方法,其底層實現(xiàn)最終還是使用Unsafe.park()方法和Unsafe.unpark()方法
//線程調(diào)用該方法,線程將一直阻塞直到超時,或者是中斷條件出現(xiàn)。
public native void park(boolean isAbsolute, long time);
//終止掛起的線程,恢復正常.java.util.concurrent包中掛起操作都是在LockSupport類實現(xiàn)的,其底層正是使用這兩個方法,
public native void unpark(Object thread);
9.內(nèi)存屏障
這里主要包括了loadFence、storeFence、fullFence等方法,這些方法是在**java **8新引入的,用于定義內(nèi)存屏障,避免代碼重排序,與Java內(nèi)存模型相關(guān)。
//在該方法之前的所有讀操作,一定在load屏障之前執(zhí)行完成
public native void loadFence();
//在該方法之前的所有寫操作,一定在store屏障之前執(zhí)行完成
public native void storeFence();
//在該方法之前的所有讀寫操作,一定在full屏障之前執(zhí)行完成,這個內(nèi)存屏障相當于上面兩個的合體功能
public native void fullFence();
10.包裝受檢異常為運行時異常
public native void throwException(Throwable var1);
11.Info 僅僅是返回一個低級別的內(nèi)存相關(guān)的信息
//獲取JVM的地址空間大小,4或者8字節(jié)
public native int addressSize();
//獲取本機內(nèi)存的頁數(shù),一般是4096字節(jié)
public native int pageSize();
12.看系統(tǒng)平均負載
//獲取系統(tǒng)的平均負載值,loadavg這個double數(shù)組將會存放負載值的結(jié)果,nelems決定樣本數(shù)量,nelems只能取值為1到3,
//分別代表最近1、5、15分鐘內(nèi)系統(tǒng)的平均負載。如果無法獲取系統(tǒng)的負載,此方法返回-1,否則返回獲取到的樣本數(shù)量(loadavg中有效的元素個數(shù))。
public native int getLoadAverage(double[] loadavg, int nelems);
package com.sankuai.meituan;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import sun.misc.Unsafe;
@Slf4j
public class UnsafeGetLoadAverageTest {
@Test
public void test() {
Unsafe unsafe = UnsafeWrapper.getUnsafe();
double[] loadAvg = new double[3];
//獲取在過去的1、5、15分鐘CPU正在處理以及等待CPU處理的進程數(shù)之和的統(tǒng)計信息
//也就是CPU使用隊列的長度的統(tǒng)計信息
unsafe.getLoadAverage(loadAvg, loadAvg.length);
log.info("loadAvg:{}", loadAvg);
}
}
參考資料
http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/687fd7c7986d/src/share/classes/sun/misc/Unsafe.java
https://leokongwq.github.io/2016/12/31/java-magic-unsafe.html
https://juejin.im/entry/595c599e6fb9a06bc6042514
http://www.importnew.com/8494.html
系統(tǒng)平均負載(Load average)與CPU利用率: https://blog.csdn.net/zhangnn5/article/details/7048889
其他
jdk測試版本信息
Rectangle類
@Data
@AllArgsConstructor
@Slf4j
private static class Rectangle {
static int count = 0;
private int h;
private int w;
private Rectangle subRectangle;
public Rectangle() {
log.info("Rectangle構(gòu)造函數(shù)");
}
}