Unsafe的使用(基于例子)

    技术2022-07-10  96

    Unsafe

    introget unsafenew instanceprivate fieldexceptionoff heap memory

    intro

    Unsafe是不开源的,但是它无所不能。它能申请堆外内存、cas、park和unpark、修改private变量等等的。

    get unsafe

    public class UnsafeUtil { public static Unsafe getUnsafe() throws Exception{ Field field = Unsafe.class.getDeclaredField("theUnsafe"); field.setAccessible(true); Unsafe unsafe = (Unsafe) field.get(null); return unsafe; } }

    通过反射获取到unsafe。

    new instance

    unsafe可以根据类对象得到实例。

    举例:

    public class InitializationOrdering { private long a; public InitializationOrdering() { this.a = 1; } public long getA() { return this.a; } } @Test public void testInitializeInstanceByNew() { InitializationOrdering initializationOrdering = new InitializationOrdering(); assertEquals(initializationOrdering.getA(), 1); }

    如果是new出来的,必然走构造器,那么getA()一定是1。

    @Test public void testInitializeInstanceByUnsafe() throws Exception { Unsafe unsafe = UnsafeUtil.getUnsafe(); InitializationOrdering instance = (InitializationOrdering) unsafe.allocateInstance(InitializationOrdering.class); assertEquals(instance.getA(), 0); }

    unsafe根据class对象拿到了实例,没有走构造器,很是神奇。

    private field

    对于私有变量,unsafe也能拿到。

    例子:

    public class SecretHolder { private int SECRET_VALUE = 0; public boolean secretIsDisclosed() { return SECRET_VALUE == 1; } } @Test public void testModifyPrivateValue() throws Exception { SecretHolder secretHolder = new SecretHolder(); Field field = secretHolder.getClass().getDeclaredField("SECRET_VALUE"); Unsafe unsafe = UnsafeUtil.getUnsafe(); unsafe.putInt(secretHolder, unsafe.objectFieldOffset(field), 1); assertTrue(secretHolder.secretIsDisclosed()); }

    unsafe成功地给私有变量赋上了值1。

    exception

    unsafe可以自己抛出异常,单纯地抛异常:

    @Test(expected = IOException.class) public void throwsAnException() throws Exception { Unsafe unsafe = UnsafeUtil.getUnsafe(); unsafe.throwException(new IOException()); }

    off heap memory

    一般我们的对象是new出来的,不管你用什么设计模式,最终还是new出来的。new出来的对象一定在堆上,是处于jvm的管辖范围的,换句话说,它能被gc。

    但是有些情况,我们要在堆外直接申请内存放置对象。它脱离了gc,并且空间可以要得很大。

    但是我们要手动释放内存。

    public class OffHeapArray { private final static int BYTE = 1; private long size; private long address; public OffHeapArray(long size) throws Exception { this.size = size; address = getUnsafe().allocateMemory(size * BYTE); } public void set(long i, byte value) throws Exception { getUnsafe().putByte(address + i * BYTE, value); } public int get(long idx) throws Exception { return getUnsafe().getByte(address + idx * BYTE); } public long size() { return size; } public void freeMemory() throws Exception { getUnsafe().freeMemory(address); } }

    构造方法:

    public OffHeapArray(long size) throws Exception { this.size = size; address = getUnsafe().allocateMemory(size * BYTE); }

    分配内存,初始化地址。

    set方法:

    public void set(long i, byte value) throws Exception { getUnsafe().putByte(address + i * BYTE, value); }

    这是往索引中放值。这个索引就是地址加上偏移量。

    get方法:

    public int get(long idx) throws Exception { return getUnsafe().getByte(address + idx * BYTE); }

    根据索引取值。

    测试:

    @Test @Ignore public void testOffHeapMemory() throws Exception { //given long SUPER_SIZE = (long) Integer.MAX_VALUE; OffHeapArray array = new OffHeapArray(SUPER_SIZE); //when 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); } long arraySize = array.size(); array.freeMemory(); //then assertEquals(arraySize, SUPER_SIZE); assertEquals(sum, 300); }

    我们申请的数组长度是整数的最大值。

    然后往数组里面不断地放值并取出来累加。

    最后一定要手动释放内存。

    Processed: 0.012, SQL: 9