Unsafe类

目录

一、概述

Unsafe类可以直接访问系统内存资源、自主管理内存资源,由于过于底层 ,不建议自己调用Unsafe类,容易造成JVM异常,但是JUC又经常使用这个类,所以有必要了解其原理。

Unsafe对象是单例的:

java 复制代码
public final class Unsafe {
  // 单例对象
  private static final Unsafe theUnsafe;
  ......
  private Unsafe() {
  }
  @CallerSensitive
  public static Unsafe getUnsafe() {
    Class var0 = Reflection.getCallerClass();
    // 仅在引导类加载器`BootstrapClassLoader`加载时才合法
    if(!VM.isSystemDomainLoader(var0.getClassLoader())) {
      throw new SecurityException("Unsafe");
    } else {
      return theUnsafe;
    }
  }
}

只能基于反射获取Unsafe对象,通过getUnsafe()方法获取会抛异常:

java 复制代码
private static Unsafe reflectGetUnsafe() {
    try {
      Field field = Unsafe.class.getDeclaredField("theUnsafe");
      field.setAccessible(true);
      return (Unsafe) field.get(null);
    } catch (Exception e) {
      log.error(e.getMessage(), e);
      return null;
    }
}

二、内存操作

Unsafe类提供了如下接口操作内存,不仅可以操作JVM堆内存,还能操作JVM外的内存。

java 复制代码
//获取内存空间,返回内存空间地址
public native long allocateMemory(long bytes);
//重新调整内存空间的大小,当address后的空间足够扩容则会原地扩容,否则拷贝数据到新的内存空间并返回地址
public native long reallocateMemory(long address, long bytes);
//将内存设置为指定值
public native void setMemory(Object o, long offset, long bytes, byte value);
//内存拷贝
public native void copyMemory(Object srcBase, long srcOffset, Object destBase, long destOffset, long bytes);
//释放内存
public native void freeMemory(long address);

1.DirectByteBuffer类

DirectByteBuffer类用于申请堆外内存 ,底层就是调用Unsafe类的API。I/O过程中会将数据从JVM堆内存拷贝到堆外内存,有一定耗时,所以将频繁I/O的数据直接存储到堆外内存能够提升性能。

java 复制代码
DirectByteBuffer(int cap) {               
    super(-1, 0, cap, cap);
    boolean pa = VM.isDirectMemoryPageAligned();
    int ps = Bits.pageSize();
    long size = Math.max(1L, (long)cap + (pa ? ps : 0));
    Bits.reserveMemory(size, cap);

    long base = 0;
    try {
        // 分配内存并返回基地址
        base = unsafe.allocateMemory(size);
    } catch (OutOfMemoryError x) {
        Bits.unreserveMemory(size, cap);
        throw x;
    }
    // 内存初始化
    unsafe.setMemory(base, size, (byte) 0);
    if (pa && (base % ps != 0)) {
        // Round up to page boundary
        address = base + ps - (base & (ps - 1));
    } else {
        address = base;
    }
    // 构建Cleaner对象用于跟踪DirectByteBuffer对象的垃圾回收,以实现当DirectByteBuffer被垃圾回收时,分配的堆外内存一起被释放
    cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
    att = null;
}

三、内存屏障

内存屏障用于确保主内存和多线程工作内存中数据的同步保证数据可见性Java线程:volatile关键字(解决可见性)

java 复制代码
//内存屏障,禁止读操作重排序。
public native void loadFence();
//内存屏障,禁止store操作重排序。
public native void storeFence();
//内存屏障,禁止读写操作重排序
public native void fullFence();
  • 写内存屏障:对于写操作,首先会修改当前线程工作内存的数据,然后将其写回主内存,但这是两步操作,期间其他线程仍然能够读到主内存的旧数据。写内存屏障就是为了立即屏障之前写操作 结果刷新到主内存 ,确保两步操作期间其他线程必须等待。并结合缓存一致性协议将其他处理器(线程工作内存)的缓存行设置为无效,但是缓存一致性协议是异步的,期间其他线程仍然能操作其工作内存中的旧数据,所以应该结合读内存屏障使用。
  • 读内存屏障:对于读操作,首先会从主内存读数据到工作内存,然后操作工作内存的数据,但是操作数据期间主内存的数据可能已经被修改了,此时工作内存中的数据是脏数据。读内存屏障就是确保在屏障之后读操作 都能看到最新的数据 。在多处理器系统中,由于缓存一致性协议不是瞬时的,一个处理器对共享数据的修改可能不会立即被其他处理器看到。其他处理器的缓存中可能还存有旧的数据,而读屏障可以强制该处理器(线程)等待缓存一致性协议将已修改的缓存失效后,才执行读屏障后的读操作,从主内存重新加载数据,从而确保读取到最新的值。
  • 全内存屏障:读屏障+写屏障,保证屏障之前的写操作对屏障之后的读操作立即可见。
java 复制代码
public class MyThread extends Thread{
    boolean flag = false;
    @Override
    public void run() {
        System.out.println("thread线程执行,flag="+flag);
        flag = true;
        reflectGetUnsafe().storeFence();
        System.out.println("thread线程执行,flag="+flag);
    }

    public static void main(String[] args) throws InterruptedException {
        MyThread thread = new MyThread();
        thread.start();
        System.out.println("main线程执行,flag="+thread.flag);
        reflectGetUnsafe().loadFence();
        System.out.println("main线程执行,flag="+thread.flag);
    }
}

四、CAS操作

CAS是一条CPU原子指令cmpxchg指令),Unsafe提供的CAS方法底层实现即为cmpxchg,用于实现乐观锁Java线程 CAS乐观锁、AtomicInteger类源码

五、数组操作

java 复制代码
//返回数组中第一个元素的地址
public native int arrayBaseOffset(Class<?> arrayClass);
//返回数组中一个元素占用的大小
public native int arrayIndexScale(Class<?> arrayClass);

1.AtomicIntegerArray类

AtomicIntegerArray可以实现对Integer数组中每个元素的原子性操作,就是通过Unsafe类的 arrayBaseOffset、arrayIndexScale进行数组中元素的定位 ,然后通过CAS实现原子性操作

六、线程调度

java 复制代码
//取消阻塞线程
public native void unpark(Object thread);
//阻塞线程
public native void park(boolean isAbsolute, long time);
//获得对象锁(可重入锁)
@Deprecated
public native void monitorEnter(Object o);
//释放对象锁
@Deprecated
public native void monitorExit(Object o);
//尝试获取对象锁
@Deprecated
public native boolean tryMonitorEnter(Object o);

1.AbstractQueuedSynchronizer类

AQS的LockSupport.park()LockSupport.unpark()方法实际是调用Unsafe类的 park、unpark方式实现的实现线程的阻塞和唤醒的:

java 复制代码
public static void park(Object blocker) {
    Thread t = Thread.currentThread();
    setBlocker(t, blocker);
    UNSAFE.park(false, 0L);
    setBlocker(t, null);
}
public static void unpark(Thread thread) {
    if (thread != null)
        UNSAFE.unpark(thread);
}
相关推荐
一只专注api接口开发的技术猿2 小时前
如何处理淘宝 API 的请求限流与数据缓存策略
java·大数据·开发语言·数据库·spring
荒诞硬汉2 小时前
对象数组.
java·数据结构
期待のcode2 小时前
Java虚拟机的非堆内存
java·开发语言·jvm
黎雁·泠崖2 小时前
Java入门篇之吃透基础语法(二):变量全解析(进制+数据类型+键盘录入)
java·开发语言·intellij-idea·intellij idea
仙俊红2 小时前
LeetCode484周赛T4
java
计算机毕设指导62 小时前
基于微信小程序的丽江市旅游分享系统【源码文末联系】
java·spring boot·微信小程序·小程序·tomcat·maven·旅游
Mr -老鬼2 小时前
Rust 的优雅和其他语言的不同之处
java·开发语言·rust
weixin_531651812 小时前
Rust 的所有权机制
java·开发语言·rust