Java:Unsafe类的使用

一、获取Unsafe类实例

1     Field field = Unsafe.class.getDeclaredField("theUnsafe");
2     field.setAccessible(true);
3     return (Unsafe) field.get(null);    

 theUnsafe 是Unsafe内部的一个字段,Unsafe在静态代码块中实例化一个Unsafe类,并通过反射获取Unsafe。

还有一个是Unsafe.getUnsafe静态方法也是获取Unsafe实例的,不过这个getUnsafe方法会检查类加载器类型,如果非Bootstrap Classloader就会抛SecurityException异常。

 二、使用Unsafe来实例化一个类

 1 class InitializationOrdering {
 2     private long a;
 3 
 4     public InitializationOrdering() {
 5         this.a = 1;
 6     }
 7 
 8     public long getA() {
 9         return this.a;
10     }
11 }

使用 new 构造函数初始化该对象时, getA() 方法将返回值1,可以使用Unsafe使用 allocateInstance() 方法。它只会为我们的类分配内存,不会调用构造函数:

 1         InitializationOrdering o3 = null;
 2         try {
 3             o3 = (InitializationOrdering) THE_UNSAFE.allocateInstance(InitializationOrdering.class);
 4             // 构造函数没有被调用, 因此, getA()方法返回了long类型的默认值——0
 5             if (o3.getA() == 0) {
 6                 System.out.println("不会调用InitializationOrdering构造函数获取到的a为0");
 7             }
 8         } catch (InstantiationException e) {
 9             e.printStackTrace();
10         }

三、改变私有字段

1 class SecretHolder {
2     private int SECRET_VALUE = 0;
3 
4     public boolean secretIsDisclosed() {
5         return SECRET_VALUE == 1;
6     }
7 }

通过Unsafe类的 putInt() 方法修改SecretHolder的私有整形字段。

 1         SecretHolder secretHolder = new SecretHolder();
 2         try {
 3             Field f = secretHolder.getClass().getDeclaredField("SECRET_VALUE");
 4 
 5             THE_UNSAFE.putInt(secretHolder, THE_UNSAFE.objectFieldOffset(f), 1);
 6 
 7             if(secretHolder.secretIsDisclosed()){
 8                 System.out.println("Unsafe改变了私有字段的值, 改成了1");
 9             }
10         } catch (NoSuchFieldException e) {
11             e.printStackTrace();
12         }

四、抛出异常

1         // 抛出异常, 使用throwException()方法抛出任何异常而不限制调用者处理该异常
2         THE_UNSAFE.throwException(new IOException());

五、堆外内存

应用程序在 JVM 上的可用内存不足,我们最终可能会迫使 GC 进程过于频繁地运行。想在GC时减少回收停顿对于应用的影响,我们需要一个特殊的内存区域,在堆外并且不受 GC 进程控制。

Unsafe的 allocateMemory() 方法能够让我们能够在堆外给一个比较大的对象分配内存,这代表这这些内存不会GC和JVM看到或者纳入它们的管理,而是由操作系统进行管理的。对于需要频繁进行内存间数据拷贝且生命周期较短的暂存数据,都建议存储到堆外内存,比如I/O通信过程。

 1 class OffHeapArray {
 2     private final static int BYTE = 1;
 3     private long size;
 4     private long address;
 5 
 6     public OffHeapArray(long size) throws NoSuchFieldException, IllegalAccessException {
 7         this.size = size;
 8         address = getUnsafe().allocateMemory(size * BYTE);
 9     }
10 
11     private Unsafe getUnsafe() throws IllegalAccessException, NoSuchFieldException {
12         Field f = Unsafe.class.getDeclaredField("theUnsafe");
13         f.setAccessible(true);
14         return (Unsafe) f.get(null);
15     }
16 
17     public void set(long i, byte value) throws NoSuchFieldException, IllegalAccessException {
18         getUnsafe().putByte(address + i * BYTE, value);
19     }
20 
21     public int get(long idx) throws NoSuchFieldException, IllegalAccessException {
22         return getUnsafe().getByte(address + idx * BYTE);
23     }
24 
25     public long size() {
26         return size;
27     }
28 
29     public void freeMemory() throws NoSuchFieldException, IllegalAccessException {
30         getUnsafe().freeMemory(address);
31     }
32 }

使用 allocateMemory 分配内存,并往分配的内存中 putByte 进去一个3(转byte),并且 getByte 循环100次并求和,最后看sum结果是否为300.。

 1         long SUPER_SIZE = (long) Integer.MAX_VALUE * 2;
 2         try {
 3             OffHeapArray array = new OffHeapArray(SUPER_SIZE);
 4 
 5             int sum = 0;
 6             // 将 N 个字节值放入这个数组,然后检索这些值,将它们相加以测试我们的寻址是否正常工作
 7             for (int i = 0; i < 100; i++) {
 8                 array.set((long) Integer.MAX_VALUE + i, (byte) 3);
 9                 sum += array.get((long) Integer.MAX_VALUE + i);
10             }
11             if (array.size() == SUPER_SIZE) {
12                 System.out.println("数组大小为SUPER_SIZE");
13             }
14 
15             if (sum == 300) {
16                 System.out.println("数组总和为300");
17             }
18         } catch (NoSuchFieldException | IllegalAccessException e) {
19             e.printStackTrace();
20         }

六、ComparedAndSwap操作

 1 class CASCount {
 2     private Unsafe unsafe;
 3     private volatile long counter = 0;
 4     private long offset;
 5 
 6     private Unsafe getUnsafe() throws IllegalAccessException, NoSuchFieldException {
 7         Field f = Unsafe.class.getDeclaredField("theUnsafe");
 8         f.setAccessible(true);
 9         return (Unsafe) f.get(null);
10     }
11 
12     CASCount() throws Exception {
13         unsafe = getUnsafe();
14         offset = unsafe.objectFieldOffset(CASCount.class.getDeclaredField("counter"));
15     }
16 
17     /**
18      * 使用Unsafe中的CompareAndSwapLong方法构建基于CAS计数器
19      */
20     public void increment() {
21         long before = counter;
22         while (!unsafe.compareAndSwapLong(this, offset, before, before + 1)) {
23             before = counter;
24         }
25     }
26 
27     public long getCounter() {
28         return counter;
29     }
30 }

获取计数器字段的地址,以便稍后在 increment 方法中使用它,while循环中使用 compareAndSwapLong 来增加先前获取的值,可以通过增加多个线程的共享计数器来测试。

 1         int NUM_OF_THREAD = 1_10;
 2         int NUM_OF_INCREMENT = 10_100;
 3         ExecutorService service = Executors.newFixedThreadPool(NUM_OF_THREAD);
 4 
 5         try {
 6             CASCount casCount = null;
 7             casCount = new CASCount();
 8             CASCount finalCasCount = casCount;
 9             IntStream.rangeClosed(0, NUM_OF_THREAD - 1)
10                     .forEach(i -> service.submit(() -> IntStream
11                             .rangeClosed(0, NUM_OF_INCREMENT - 1)
12                             .forEach(j -> finalCasCount.increment())));
13             System.out.println("NUM_OF_THREAD:" + NUM_OF_THREAD);
14             System.out.println("NUM_OF_INCREMENT:"+ NUM_OF_INCREMENT);
15             System.out.println(NUM_OF_THREAD * NUM_OF_INCREMENT == casCount.getCounter());
16         } catch (Exception e) {
17             e.printStackTrace();
18         }

七、Park & Unpark

Unsafe API 中有两种的方法,JVM 使用它们来上下文切换线程。当线程正在等待某个动作时,JVM 可以使用Unsafe类中的 park() 方法使该线程阻塞。它与 Object.wait() 方法非常相似,但它调用本机操作系统代码,从而利用一些架构细节来获得最佳性能。当线程被阻塞并需要再次运行时,JVM 使用 unpark() 方法。

 

posted @ 2022-01-05 21:28  賣贾笔的小男孩  阅读(333)  评论(0编辑  收藏  举报