Java UnSafe 类使用介绍

1. UnSafe 简介

UnsafeJava 整个并发包底层实现的核心,它具有像 C++ 的指针一样直接操作内存的能力,而这也就意味着其越过了 JVM 的限制。Unsafe 的特性可归结如下,它虽然在一定程度上提升了效率,但是也带来了指针的不安全性

  1. Unsafe 不受 JVM 管理,也就无法被自动 GC,需要手动 GC,容易出现内存泄漏
  2. Unsafe 的大部分方法中必须提供原始地址(内存地址)和被替换对象的地址,偏移量需自行计算,一旦出现问题必然是 JVM 崩溃级别的异常,会导致整个应用程序直接 crash
  3. 直接操作内存,也意味着其速度更快,在高并发的条件之下能够很好地提高效率

2. UnSafe 功能与使用示例

Unsafe 类的构造函数是私有的,而对外提供的静态方法Unsafe#getUnsafe() 又对调用者的 ClassLoader 有限制 ,如果这个方法的调用者不是由 Boot ClassLoader 加载的,则会报错

public static Unsafe getUnsafe() {
        Class var0 = Reflection.getCallerClass();
        if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
            throw new SecurityException("Unsafe");
        } else {
            return theUnsafe;
        }
    }

因为 Java 源码中由开发者自定义的类都是由 Appliaction ClassLoader 加载的,所以正常情况下我们无法直接使用Unsafe ,如果需要使用它,则需要利用反射

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

2.1 操作对象属性

方法功能
public native Object getObject(Object o, long offset)获取一个Java 对象中偏移地址为 offset 的属性的值,此方法可以突破修饰符的限制,类似的方法有 getInt()、getDouble()等,同理还有 putObject() 方法
public native Object getObjectVolatile(Object o, long offset)强制从主存中获取目标对象指定偏移量的属性值,类似的方法有getIntVolatile()、getDoubleVolatile()等,同理还有putObjectVolatile()
public native void putOrderedObject(Object o, long offset, Object x)设置目标对象中偏移地址 offset 对应的对象类型属性的值为指定值。这是一个有序或者有延迟的 putObjectVolatile()方法,并且不保证值的改变被其他线程立即看到。只有在属性被volatile修饰并且期望被修改的时候使用才会生效,类似的方法有 putOrderedInt() 和 putOrderedLong()
public native long objectFieldOffset(Field f)返回给定的非静态属性在它的类的存储分配中的位置(偏移地址),然后可根据偏移地址直接对属性进行修改,可突破属性的访问修饰符限制

private String name;

@Test
public void test() {
        Unsafe unsafe = getUnsafe();
        try {
            DirectMemory directMemory = (DirectMemory) unsafe.allocateInstance(DirectMemory.class);
            long nameOffset = unsafe.objectFieldOffset(DirectMemory.class.getDeclaredField("name"));
            unsafe.putObject(directMemory, nameOffset, "luck");
            System.out.println(directMemory.getName());
        } catch (Exception e) {
            e.printStackTrace();
        }
}

2.2 操作数组元素

方法功能
public native int arrayBaseOffset(Class arrayClass)返回数组类型的第一个元素的偏移地址(基础偏移地址)
public native int arrayIndexScale(Class arrayClass)返回数组中元素与元素之间的偏移地址的增量,配合 arrayBaseOffset() 使用就可以定位到任何一个元素的地址

private String[] names = {"nathan", "goog", "luck"};
  
@Test
public void test() {
        Unsafe unsafe = getUnsafe();
        try {
             Class<?> a = String[].class;
             int base = unsafe.arrayBaseOffset(a);
             int scale = unsafe.arrayIndexScale(a);
             // base + i * scale 即为字符串数组下标 i 在对象的内存中的偏移地址
             System.out.println(unsafe.getObject(names, (long) base + 2 * scale));
        } catch (Exception e) {
            e.printStackTrace();
        }
}

ForkJoinPool 中有一种新的元素定位算法,这种方法要求 scale 也就是元素的内存大小为 2 的次幂。假如 scale 等于 4,那么 ASHIFT 值为 2,乘以 4 和向左移 2 位结果是一样的,但是移位操作显然更高效

ASHIFT = 31 - Integer.numberOfLeadingZeros(scale)
offset = (i << ASHIFT) + ABASE

2.3 内存地址操作

方法功能
public native int addressSize()获取本地指针的大小(单位是byte),通常值为 4 或者 8
public native int pageSize()获取本地内存的页数,此值为2的幂次方
public native long allocateMemory(long bytes)分配一块新的本地内存,通过bytes指定内存块的大小(单位是byte),返回新开辟的内存的地址
public native long reallocateMemory(long address, long bytes)通过指定的内存地址 address 重新调整本地内存块的大小,调整后的内存块大小通过bytes指定(单位为byte)
public native void setMemory(Object o, long offset, long bytes, byte value)将给定内存块中的所有字节设置为固定值(通常是0)
public class DirectMemoryBuffer {
    private final static int BYTE = 1;
    private long size;
    private long address;
    private Unsafe unsafe;

    public DirectMemoryBuffer(long size, Unsafe unsafe) {
        this.size = size;
        this.unsafe = unsafe;
        address = unsafe.allocateMemory(size * BYTE);
    }

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

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

    public long size() {
        return size;
    }

    public void freeMemory() {
        unsafe.freeMemory(address);
    }
}

2.4 CAS 操作

CAS 是一种乐观锁机制,它不需要抢占锁,能有效地提高效率,依赖于硬件的原子操作实现

方法功能
public final native boolean compareAndSwapObject(Object target, long offset, Object exceptData, Object targetData)其作用为比较目标对象指定偏移量的属性的期望值与主存中的值,如果二者相等,则将主存中的值更新为目标值。同样还有 compareAndSwapInt() 和 compareAndSwapLong()
public class CASCounter {
    private Unsafe unsafe;
    // count 需要声明为 volatile 来保证对所有线程可见
    private volatile long count = 0;
    private long offset;

    public CASCounter(Unsafe unsafe) {
        this.unsafe = unsafe;
        try {
            offset = unsafe.objectFieldOffset(CASCounter.class.getDeclaredField("count"));
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }

    // 关键的 increment 方法,其在 while 循环里不断尝试调用 compareAndSwapLong,在方法内部累加count的同时
    // 检查其值有没有被其他线程改变。如没有,就提交更改,如果不一致,那么继续尝试提交更改
    public void increment() {
        long before = count;
        while (!unsafe.compareAndSwapLong(this, offset, before, before + 1)) {
            before = count;
        }
    }

    public long getCount() {
        return count;
    }
}

2.5 线程挂起与恢复

方法功能
public native void park(boolean isAbsolute, long time)阻塞当前线程,一直等到 unpark()方法被调用或者超时,和 Object.await()非常类似,但是 park 是操作系统调用,因此在某些操作系统架构上这会带来更好的性能
public native void unpark(Object thread)唤醒被 park 阻塞的线程,由于其不安全性,因此必须保证线程是存活的

2.6 内存屏障

方法功能
public native void loadFence()如果不要volatile去增加内存屏障,即可用该方法手动增加屏障。在该方法之前的所有读操作,一定在load屏障之前执行完成
public native void storeFence()在该方法之前的所有写操作,一定在store屏障之前执行完成
public native void fullFence()在该方法之前的所有读写操作,一定在 full 屏障之前执行完成,这个内存屏障相当于上面两个(load屏障和store屏障)的合体功能

2.7 Class 相关

方法功能
public native boolean shouldBeInitialized(Class<?> c)判断是否需要初始化一个类,通常在获取一个类的静态属性的时候使用(一个类如果没初始化,它的静态属性也不会初始化)。 当且仅当ensureClassInitialized方法不生效时返回false
public native void ensureClassInitialized(Class<?> c)检测给定的类是否已经初始化,通常在获取一个类的静态属性的时候使用(因为一个类如果没初始化,它的静态属性也不会初始化)
public native Class<?> defineClass(String name, byte[] b, int off, int len, ClassLoader loader, ProtectionDomain protectionDomain)定义一个类,此方法会跳过JVM的所有安全检查,默认情况下 ClassLoader(类加载器)和 ProtectionDomain(保护域)实例来源于调用者
public native Class<?> defineAnonymousClass(Class<?> hostClass, byte[] data, Object[] cpPatches)定义一个匿名类
public native long staticFieldOffset(Field f)返回给定的静态属性在它的类的存储分配中的位置(偏移地址)
public native Object staticFieldBase(Field f)返回给定的静态属性的位置,配合 staticFieldOffset()方法使用
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值