sun.misc.Unsafe 指南

1.概述

来自sun.misc包的不安全。此类为我们提供了低级机制,这些机制仅供核心Java库使用,而不是由标准用户使用。在本文中,我们查看了不安全的类及其最有用的构造。我们了解了如何访问私有字段,如何分配堆外内存,以及如何使用比较并交换构造来实现无锁算法。

  • 1.1获取不安全类的实例获取实例的是通过他的静态方法getUnsafe()。需要注意的是,默认情况下,会抛出SecurityException。通过反射获取实例:
 Field f = sun 。misc 。不安全。上课。getDeclaredField (“theUnsafe” ); 
f 。setAccessible (true; 
太阳。misc 。不安全不安全=  (太阳。杂项。不安全) ˚F 。get ( null ); 
系统。出。println (不安全);
  • 1.2获取Unsafe的实例化类

java class InitializationOrdering { private long a ; public InitializationOrdering (){ this 。a = 1 ; } 公共长木屐(){ 返回此。一个; } }

当我们使用构造函数初始化该对象时,木屐()方法将返回值1:

InitializationOrdering o1 = new InitializationOrdering();
assertEquals(o1.getA(), 1);

但是我们可以使用Unsafe来使用allocateInstance()方法。它只会为我们的类分配内存,并且不会调用构造函数:

InitializationOrdering o3 
  = (InitializationOrdering) unsafe.allocateInstance(InitializationOrdering.class);
  
assertEquals(o3.getA(), 0);

请注意,未调用构造函数,并且由于这一事实,getA()方法返回long类型的默认值- 即0。

  • 4.改变私有属性
    假设我们有一个私有属性的类:
class SecretHolder {
    private int SECRET_VALUE = 0;
 
    public boolean secretIsDisclosed() {
        return SECRET_VALUE == 1;
    }
}

使用Unsafe中的putInt 方法,我们可以更改私有SECRET_VALUE字段的值,更改/破坏该实例的状态:

SecretHolder secretHolder = new SecretHolder();
 
Field f = secretHolder.getClass().getDeclaredField("SECRET_VALUE");
unsafe.putInt(secretHolder, unsafe.objectFieldOffset(f), 1);
 
assertTrue(secretHolder.secretIsDisclosed());

一旦我们通过反射调用得到一个字段,我们就可以使用Unsafe将其值更改为任何其他int值。

  • 抛出异常
    编译器不会以与常规Java代码相同的方式检查通过Unsafe调用的代码。我们可以使用throwException()方法抛出任何异常,而不会限制调用者处理该异常,即使它是一个已检查的异常:
@Test(expected = IOException.class)
public void givenUnsafeThrowException_whenThrowCheckedException_thenNotNeedToCatchIt() {
    unsafe.throwException(new IOException());
}

在抛出一个被检查的IOException之后,我们不需要捕获它,也不需要在方法声明中指定它。

  • 堆外内存

如果应用程序的JVM上的可用内存不足,我们最终可能会迫使GC进程过于频繁地运行。理想情况下,我们需要一个特殊的内存区域,堆外并且不受GC进程控制。

来自Unsafe类的allocateMemory()方法使我们能够从堆中分配大对象,这意味着GC和JVM不会看到并考虑这个内存。

这可能非常有用,但我们需要记住,这个内存需要手动管理,并在不再需要时使用freeMemory()正确回收。

假设我们想要创建大型的堆外存储器字节数组。我们可以使用allocateMemory()方法来实现:

class OffHeapArray {
	    private final static int BYTE = 1;
	    private long size;
	    private long address;
	 
	    public OffHeapArray(long size) throws NoSuchFieldException, IllegalAccessException {
	        this.size = size;
	        address = getUnsafe().allocateMemory(size * BYTE);
	    }
	 
	    private Unsafe getUnsafe() throws IllegalAccessException, NoSuchFieldException {
	        Field f = Unsafe.class.getDeclaredField("theUnsafe");
	        f.setAccessible(true);
	        return (Unsafe) f.get(null);
	    }
	 
	    public void set(long i, byte value) throws NoSuchFieldException, IllegalAccessException {
	        getUnsafe().putByte(address + i * BYTE, value);
	    }
	 
	    public int get(long idx) throws NoSuchFieldException, IllegalAccessException {
	        return getUnsafe().getByte(address + idx * BYTE);
	    }
	 
	    public long size() {
	        return size;
	    }
	     
	    public void freeMemory() throws NoSuchFieldException, IllegalAccessException {
	        getUnsafe().freeMemory(address);
	    }
    }

在OffHeapArray的构造函数中,我们正在初始化给定大小的数组。我们在地址字段中存储数组的起始地址。的组()方法正在所述索引和所述给定的值将存储在数组中。所述的get()方法检索使用其指数是一个从阵列的起始地址偏移的字节值。

接下来,我们可以使用其构造函数分配该堆外数组:

java long SUPER_SIZE = (long )整数。MAX_VALUE * 2 ; OffHeapArray array = new OffHeapArray ( SUPER_SIZE );

我们可以将Ñ个字节值放入此数组中,然后检索这些值,将它们相加以测试我们的地址是否正常工作:

int sum = 0;
for (int i = 0; i < 100; i++) {
    array.set((long) Integer.MAX_VALUE + i, (byte) 3);
    sum += array.get((long) Integer.MAX_VALUE + i);
}
 
assertEquals(array.size(), SUPER_SIZE);
assertEquals(sum, 300);

最后,我们需要通过调用freeMemory()将内存释放回操作系统。

  • CompareAndSwap操作
    java.concurrent包中非常有效的构造(如AtomicInteger)正在使用下面的Unsafe中的compareAndSwap()方法,以提供最佳性能。这种结构广泛用于无锁算法,与Java中的标准悲观同步机制相比,它可以利用CAS处理器指令提供极大的加速。

我们可以使用Unsafe中的compareAndSwapLong()方法构建基于CAS的计数器:

class CASCounter {
    private Unsafe unsafe;
    private volatile long counter = 0;
    private long offset;
 
    private Unsafe getUnsafe() throws IllegalAccessException, NoSuchFieldException {
        Field f = Unsafe.class.getDeclaredField("theUnsafe");
        f.setAccessible(true);
        return (Unsafe) f.get(null);
    }
 
    public CASCounter() throws Exception {
        unsafe = getUnsafe();
        offset = unsafe.objectFieldOffset(CASCounter.class.getDeclaredField("counter"));
    }
 
    public void increment() {
        long before = counter;
        while (!unsafe.compareAndSwapLong(this, offset, before, before + 1)) {
            before = counter;
        }
    }
 
    public long getCounter() {
        return counter;
    }
}

我们可以通过从多个线程递增共享计数器来测试我们的代码:

 
 

  
 
 
  submit(() -> IntStream
    .rangeClosed(0, NUM_OF_INCREMENTS - 1)
    .forEach(j -> casCounter.increment())));

接下来,为了断言计数器的状态是正确的,我们可以从中获取计数器值:

的java的assertEquals(NUM_OF_INCREMENTS * NUM_OF_THREADS,casCounter .getCounter());

  • 8.Park / Unpark

在JVM用于上下文切换线程的不安全API中有两种引人入胜的方法。当线程正在等待某个操作时,JVM可以使用不安全类中的 ‘公园’()方法阻止此线程。

它与的的的的Object.wait()方法非常相似,但它调用本机操作系统代码,因此利用一些体系结构细节来获得最佳性能。

当线程被阻塞并且需要再次运行时,JVM使用取消驻留()方法。我们经常会在线程转储中看到这些方法调用,尤其是在使用线程池的应用程序中。具体可以参看例子


小参考:

https://www.baeldung.com/java-unsafe

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值