天天看點

java中Unsafe使用講解

前段時間因為看JUC的源碼,裡面有大量關于unsafe的操作,是以就來看看了.寫點筆記總結下(本文基于jdk1.8):

unsafe可以幫我們直接去操作硬體資源,當然了是借助java的jit來進行的,官方不推薦使用,因為不安全,例如你使用unsafe建立一個超級大的數組,但是這個數組jvm是不管理的,隻能你自己操作,容易oom,也不利于資源的回收.

好了,下面我們來看代碼,

1.擷取unsafe

  1. ​//1.最簡單的使用方式是基于反射擷取Unsafe執行個體​

  2. ​Field f = Unsafe.class.getDeclaredField("theUnsafe");​

  3. ​f.setAccessible(true);​

  4. ​Unsafe unsafe = (Unsafe) f.get(null);​

2.擷取unsafe

  1. ​private static Unsafe unsafe = null;​

  2. ​private static Field getUnsafe = null;​

  3. ​static {​

  4. ​try {​

  5. ​getUnsafe = Unsafe.class.getDeclaredField("theUnsafe");​

  6. ​getUnsafe.setAccessible(true);​

  7. ​unsafe = (Unsafe) getUnsafe.get(null);​

  8. ​} catch (NoSuchFieldException e) {​

  9. ​e.printStackTrace();​

  10. ​} catch (IllegalAccessException e) {​

  11. ​e.printStackTrace();​

  12. ​}​

  13. ​}​

随便隻要你高興,都可以擷取到unfase,因為涉及到unfase 的權限問題,是以,我們隻能使用這種方式擷取,不然就是權限異常,

操作方法:

  1. ​/**​

  2. ​* 操作數組:​

  3. ​* 可以擷取數組的在内容中的基本偏移量(arrayBaseOffset),擷取數組内元素的間隔(比例),​

  4. ​* 根據數組對象和偏移量擷取元素值(getObject),設定數組元素值(putObject),示例如下。​

  5. ​*/​

  6. ​String[] strings = new String[]{"1", "2", "3"};​

  7. ​long i = unsafe.arrayBaseOffset(String[].class);​

  8. ​System.out.println("string[] base offset is :" + i);​

  9. ​//every index scale​

  10. ​long scale = unsafe.arrayIndexScale(String[].class);​

  11. ​System.out.println("string[] index scale is " + scale);​

  12. ​//print first string in strings[]​

  13. ​System.out.println("first element is :" + unsafe.getObject(strings, i));​

  14. ​//set 100 to first string​

  15. ​unsafe.putObject(strings, i + scale * 0, "100");​

  16. ​//print first string in strings[] again​

  17. ​System.out.println("after set ,first element is :" + unsafe.getObject(strings, i + scale * 0));​

  1. ​/**​

  2. ​* 對象操作​

  3. ​* 執行個體化Data​

  4. ​*​

  5. ​* 可以通過類的class對象建立類對象(allocateInstance),擷取對象屬性的偏移量(objectFieldOffset)​

  6. ​* ,通過偏移量設定對象的值(putObject)​

  7. ​*​

  8. ​* 對象的反序列化​

  9. ​* 當使用架構反序列化或者建構對象時,會假設從已存在的對象中重建,你期望使用反射來調用類的設定函數,​

  10. ​* 或者更準确一點是能直接設定内部字段甚至是final字段的函數。問題是你想建立一個對象的執行個體,​

  11. ​* 但你實際上又不需要構造函數,因為它可能會使問題更加困難而且會有副作用。​

  12. ​*​

  13. ​*/​

  14. ​//調用allocateInstance函數避免了在我們不需要構造函數的時候卻調用它​

  15. ​Data data = (Data) unsafe.allocateInstance(Data.class);​

  16. ​data.setId(1L);​

  17. ​data.setName("unsafe");​

  18. ​System.out.println(data);​

  19. ​//傳回成員屬性在記憶體中的位址相對于對象記憶體位址的偏移量​

  20. ​Field nameField = Data.class.getDeclaredField("name");​

  21. ​long fieldOffset = unsafe.objectFieldOffset(nameField);​

  22. ​//putLong,putInt,putDouble,putChar,putObject等方法,直接修改記憶體資料(可以越過通路權限)​

  23. ​unsafe.putObject(data,fieldOffset,"這是新的值");​

  24. ​System.out.println(data.getName());​

  25. ​/**​

  26. ​* 我們可以在運作時建立一個類,比如從已編譯的.class檔案中。将類内容讀取為位元組數組,​

  27. ​* 并正确地傳遞給defineClass方法;當你必須動态建立類,而現有代碼中有一些代理, 這是很有用的​

  28. ​*/​

  29. ​File file = new File("C:\\workspace\\idea2\\disruptor\\target\\classes\\com\\onyx\\distruptor\\test\\Data.class");​

  30. ​FileInputStream input = new FileInputStream(file);​

  31. ​byte[] content = new byte[(int)file.length()];​

  32. ​input.read(content);​

  33. ​Class c = unsafe.defineClass(null, content, 0, content.length,null,null);​

  34. ​c.getMethod("getId").invoke(c.newInstance(), null);​

  35. ​/**​

  36. ​* 記憶體操作​

  37. ​* 可以在Java記憶體區域中配置設定記憶體(allocateMemory),設定記憶體(setMemory,用于初始化),​

  38. ​* 在指定的記憶體位置中設定值(putInt\putBoolean\putDouble等基本類型)​

  39. ​*/​

  40. ​//配置設定一個8byte的記憶體​

  41. ​long address = unsafe.allocateMemory(8L);​

  42. ​//初始化記憶體填充1​

  43. ​unsafe.setMemory(address, 8L, (byte) 1);​

  44. ​//測試輸出​

  45. ​System.out.println("add byte to memory:" + unsafe.getInt(address));​

  46. ​//設定0-3 4個byte為0x7fffffff​

  47. ​unsafe.putInt(address, 0x7fffffff);​

  48. ​//設定4-7 4個byte為0x80000000​

  49. ​unsafe.putInt(address + 4, 0x80000000);​

  50. ​//int占用4byte​

  51. ​System.out.println("add byte to memory:" + unsafe.getInt(address));​

  52. ​System.out.println("add byte to memory:" + unsafe.getInt(address + 4));​

  1. ​/**​

  2. ​* CAS操作​

  3. ​* Compare And Swap(比較并交換),當需要改變的值為期望的值時,那麼就替換它為新的值,是原子​

  4. ​* (不可在分割)的操作。很多并發架構底層都用到了CAS操作,CAS操作優勢是無鎖,可以減少線程切換耗費​

  5. ​* 的時間,但CAS經常失敗運作容易引起性能問題,也存在ABA問題。在Unsafe中包含compareAndSwapObject、​

  6. ​* compareAndSwapInt、compareAndSwapLong三個方法,compareAndSwapInt的簡單示例如下。​

  7. ​*/​

  8. ​Data data = new Data();​

  9. ​data.setId(1L);​

  10. ​Field id = data.getClass().getDeclaredField("id");​

  11. ​long l = unsafe.objectFieldOffset(id);​

  12. ​id.setAccessible(true);​

  13. ​//比較并交換,比如id的值如果是所期望的值1,那麼就替換為2,否則不做處理​

  14. ​unsafe.compareAndSwapLong(data,1L,1L,2L);​

  15. ​System.out.println(data.getId());​

  1. ​/**​

  2. ​* 常量擷取​

  3. ​*​

  4. ​* 可以擷取位址大小(addressSize),頁大小(pageSize),基本類型數組的偏移量​

  5. ​* (Unsafe.ARRAY_INT_BASE_OFFSET\Unsafe.ARRAY_BOOLEAN_BASE_OFFSET等)、​

  6. ​* 基本類型數組内元素的間隔(Unsafe.ARRAY_INT_INDEX_SCALE\Unsafe.ARRAY_BOOLEAN_INDEX_SCALE等)​

  7. ​*/​

  8. ​//get os address size​

  9. ​System.out.println("address size is :" + unsafe.addressSize());​

  10. ​//get os page size​

  11. ​System.out.println("page size is :" + unsafe.pageSize());​

  12. ​//int array base offset​

  13. ​System.out.println("unsafe array int base offset:" + Unsafe.ARRAY_INT_BASE_OFFSET);​

  14. ​/**​

  15. ​* 線程許可​

  16. ​* 許可線程通過(park),或者讓線程等待許可(unpark),​

  17. ​*/​

  18. ​Thread packThread = new Thread(() -> {​

  19. ​long startTime = System.currentTimeMillis();​

  20. ​//納秒,相對時間park​

  21. ​unsafe.park(false,3000000000L);​

  22. ​//毫秒,絕對時間park​

  23. ​//unsafe.park(true,System.currentTimeMillis()+3000);​

  24. ​System.out.println("main thread end,cost :"+(System.currentTimeMillis()-startTime)+"ms");​

  25. ​});​

  26. ​packThread.start();​

  27. ​TimeUnit.SECONDS.sleep(1);​

  28. ​//注釋掉下一行後,線程3秒數後進行輸出,否則在1秒後輸出​

  29. ​unsafe.unpark(packThread);​

  1. ​/**​

  2. ​* Java數組大小的最大值為Integer.MAX_VALUE。使用直接記憶體配置設定,我們建立的數組大小受限于堆大小;​

  3. ​* 實際上,這是堆外記憶體(off-heap memory)技術,在java.nio包中部分可用;​

  4. ​*​

  5. ​* 這種方式的記憶體配置設定不在堆上,且不受GC管理,是以必須小心Unsafe.freeMemory()的使用。​

  6. ​* 它也不執行任何邊界檢查,是以任何非法通路可能會導緻JVM崩潰​

  7. ​*/​

  8. ​public class SuperArray {​

  9. ​private static Unsafe unsafe = null;​

  10. ​private static Field getUnsafe = null;​

  11. ​static {​

  12. ​try {​

  13. ​getUnsafe = Unsafe.class.getDeclaredField("theUnsafe");​

  14. ​getUnsafe.setAccessible(true);​

  15. ​unsafe = (Unsafe) getUnsafe.get(null);​

  16. ​} catch (NoSuchFieldException e) {​

  17. ​e.printStackTrace();​

  18. ​} catch (IllegalAccessException e) {​

  19. ​e.printStackTrace();​

  20. ​}​

  21. ​}​

  22. ​private final static int BYTE = 1;​

  23. ​private long size;​

  24. ​private long address;​

  25. ​public SuperArray(long size) {​

  26. ​this.size = size;​

  27. ​address = unsafe.allocateMemory(size * BYTE);​

  28. ​}​

  29. ​public void set(long i, byte value) {​

  30. ​unsafe.putByte(address + i * BYTE, value);​

  31. ​}​

  32. ​public int get(long idx) {​

  33. ​return unsafe.getByte(address + idx * BYTE);​

  34. ​}​

  35. ​public long size() {​

  36. ​return size;​

  37. ​}​

  38. ​public static void main(String[] args) {​

  39. ​long SUPER_SIZE = (long)Integer.MAX_VALUE * 2;​

  40. ​SuperArray array = new SuperArray(SUPER_SIZE);​

  41. ​System.out.println("Array size:" + array.size()); // 4294967294​

  42. ​int sum=0;​

  43. ​for (int i = 0; i < 100; i++) {​

  44. ​array.set((long)Integer.MAX_VALUE + i, (byte)3);​

  45. ​sum += array.get((long)Integer.MAX_VALUE + i);​

  46. ​}​

  47. ​System.out.println(sum);​

  48. ​}​

  49. ​}​