1. 程式人生 > >利用sun.misc.Unsafe獲取類字段的偏移地址和讀取字段的值

利用sun.misc.Unsafe獲取類字段的偏移地址和讀取字段的值

com 如何 string ring rep 最好 lar 計算 .get

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

[java] view plain copy
  1. public class VO
  2. {
  3. public int a = 0;
  4. public long b = 0;
  5. public static String c= "123";
  6. public static Object d= null;
  7. public static int e = 100;
  8. }


1.獲取實例字段的偏移地址

[java] view plain copy
  1. // 獲取實例字段的偏移地址,偏移最小的那個字段(僅挨著頭部)就是對象頭的大小
  2. System.out.println(unsafe.objectFieldOffset(VO.class.getDeclaredField("a")));
  3. System.out.println(unsafe.objectFieldOffset(VO.class.getDeclaredField("b")));
  4. // fieldOffset與objectFieldOffset功能一樣,fieldOffset是過時方法,最好不要再使用
  5. System.out.println(unsafe.fieldOffset(VO.class.getDeclaredField("b")));


2.獲取數組的頭部大小和元素大小

[java] view plain copy
  1. // 數組第一個元素的偏移地址,即數組頭占用的字節數
  2. int[] intarr = new int[0];
  3. System.out.println(unsafe.arrayBaseOffset(intarr.getClass()));
  4. // 數組中每個元素占用的大小
  5. 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.獲取類的靜態字段偏移

[java] view plain copy
  1. // 獲取類的靜態字段偏地址
  2. System.out.println(unsafe.staticFieldOffset(VO.class.getDeclaredField("c")));
  3. System.out.println(unsafe.staticFieldOffset(VO.class.getDeclaredField("d")));
  4. // 獲取靜態字段的起始地址,通過起始地址和偏移地址,就可以獲取靜態字段的值了
  5. // 只不過靜態字段的起始地址,類型不是long,而是Object類型
  6. Object base1 = unsafe.staticFieldBase(VO.class);
  7. Object base2 = unsafe.staticFieldBase(VO.class.getDeclaredField("d"));
  8. System.out.println(base1==base2);//true


4.獲取操作系統的位數

[java] view plain copy
  1. // Report the size in bytes of a native pointer.
  2. // 返回4或8,代表是32位還是64位操作系統。
  3. System.out.println(unsafe.addressSize());
  4. // 返回32或64,獲取操作系統是32位還是64位
  5. System.out.println(System.getProperty("sun.arch.data.model"));



通過上面的幾段代碼,我們可以成功獲取類中各個字段的偏移地址,這跟jol工具的輸出結果和我們的結論是一致的。有了字段的偏移地址,在加上對象的起始地,我們就能夠通過Unsafe直接獲取字段的值了。


5.讀取對象實例字段的值

[java] view plain copy
  1. //獲取實例字段的屬性值
  2. VO vo = new VO();
  3. vo.a = 10000;
  4. long aoffset = unsafe.objectFieldOffset(VO.class.getDeclaredField("a"));
  5. int va = unsafe.getInt(vo, aoffset);
  6. System.out.println("va="+va);


6.獲取靜態字段的屬性值

[java] view plain copy
  1. VO.e = 1024;
  2. Field sField = VO.class.getDeclaredField("e");
  3. Object base = unsafe.staticFieldBase(sField);
  4. long offset = unsafe.staticFieldOffset(sField);
  5. System.out.println(unsafe.getInt(base, offset));//1024


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

利用sun.misc.Unsafe獲取類字段的偏移地址和讀取字段的值