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() 方法。