1. 程式人生 > >java物件的記憶體佈局(二):利用sun.misc.Unsafe獲取類欄位的偏移地址和讀取欄位的值

java物件的記憶體佈局(二):利用sun.misc.Unsafe獲取類欄位的偏移地址和讀取欄位的值

上一篇文章中,我們列出了計算java物件大小的幾個結論以及jol工具的使用,jol工具的原始碼有興趣的可以去看下。現在我們利用JDK中的sun.misc.Unsafe來計算下欄位的偏移地址,一則驗證下之前文章中的結論,再則跟jol輸出結果對比下。如何獲取sun.misc.Unsafe物件,可以參考這篇文章

public class VO
{
	public int a = 0;
	
	public long b = 0;
	
	public static String c= "123";
	
	public static Object d= null;
	
	public static int e = 100;
}

1.獲取例項欄位的偏移地址
// 獲取例項欄位的偏移地址,偏移最小的那個欄位(僅挨著頭部)就是物件頭的大小
System.out.println(unsafe.objectFieldOffset(VO.class.getDeclaredField("a")));
System.out.println(unsafe.objectFieldOffset(VO.class.getDeclaredField("b")));

// fieldOffset與objectFieldOffset功能一樣,fieldOffset是過時方法,最好不要再使用
System.out.println(unsafe.fieldOffset(VO.class.getDeclaredField("b")));

2.獲取陣列的頭部大小和元素大小

// 陣列第一個元素的偏移地址,即陣列頭佔用的位元組數
int[] intarr = new int[0];
System.out.println(unsafe.arrayBaseOffset(intarr.getClass()));

// 陣列中每個元素佔用的大小
System.out.println(unsafe.arrayIndexScale(intarr.getClass()));
Unsafe類中有很多以BASE_OFFSET結尾的常量,比如ARRAY_INT_BASE_OFFSET等,這些常量值是通過arrayBaseOffset方法得到的。arrayBaseOffset方法是一個本地方法,可以獲取陣列第一個元素的偏移地址。Unsafe類中還有很多以INDEX_SCALE結尾的常量,比如 ARRAY_INT_INDEX_SCALE 等,這些常量值是通過arrayIndexScale方法得到的。將arrayBaseOffset與arrayIndexScale配合使用,可以定位陣列中每個元素在記憶體中的位置。


3.獲取類的靜態欄位偏移

// 獲取類的靜態欄位偏地址
System.out.println(unsafe.staticFieldOffset(VO.class.getDeclaredField("c")));
System.out.println(unsafe.staticFieldOffset(VO.class.getDeclaredField("d")));

// 獲取靜態欄位的起始地址,通過起始地址和偏移地址,就可以獲取靜態欄位的值了
// 只不過靜態欄位的起始地址,型別不是long,而是Object型別
Object base1 = unsafe.staticFieldBase(VO.class);
Object base2 = unsafe.staticFieldBase(VO.class.getDeclaredField("d"));
System.out.println(base1==base2);//true

4.獲取作業系統的位數
//  Report the size in bytes of a native pointer.
//  返回4或8,代表是32位還是64位作業系統。
System.out.println(unsafe.addressSize());
// 返回32或64,獲取作業系統是32位還是64位
System.out.println(System.getProperty("sun.arch.data.model"));
通過上面的幾段程式碼,我們可以成功獲取類中各個欄位的偏移地址,這跟jol工具的輸出結果和我們的結論是一致的。有了欄位的偏移地址,在加上物件的起始地,我們就能夠通過Unsafe直接獲取欄位的值了。

5.讀取物件例項欄位的值

//獲取例項欄位的屬性值
VO vo = new VO();
vo.a = 10000;
long aoffset = unsafe.objectFieldOffset(VO.class.getDeclaredField("a"));
int va = unsafe.getInt(vo, aoffset);
System.out.println("va="+va);

6.獲取靜態欄位的屬性值
VO.e = 1024;
Field sField = VO.class.getDeclaredField("e");
Object base = unsafe.staticFieldBase(sField);
long offset = unsafe.staticFieldOffset(sField);
System.out.println(unsafe.getInt(base, offset));//1024

可以看到Unsafe功能是很強大的,位java語言提供了更底層的功能。