java-其他-Unsafe

获取Unsafe类实例

如下Unsafe源码所示,Unsafe类为一单例实现,提供静态方法getUnsafe获取Unsafe实例,当且仅当调用getUnsafe方法的类为引导类加载器所加载时才合法,否则抛出SecurityException异常。

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;
    }
  }
}

该如何获取其实例?有如下两个可行方案。

其一,从getUnsafe方法的使用限制条件出发,通过Java命令行命令-Xbootclasspath/a把调用Unsafe相关方法的类A所在jar包路径追加到默认的bootstrap路径中,使得A被引导类加载器加载,从而通过Unsafe.getUnsafe方法安全的获取Unsafe实例。

java -Xbootclasspath/a:${path} com.zc.Test  // 其中path为调用Unsafe相关方法的类所在jar包路径

其二,通过反射获取单例对象theUnsafe。

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提供的接口

堆外内存的分配、拷贝、释放、给定地址值操作等方法。

//分配内存
public native long allocateMemory(long bytes);
//扩充内存
public native long reallocateMemory(long address, long bytes);
//释放内存
public native void freeMemory(long address);

//在给定内存块中设置值
//如果o为null,则offset为绝对地址
public native void setMemory(Object o, long offset, long bytes, byte value);
//相当于上面的o为null
public native void setMemory(long address, long bytes, byte value);

//内存拷贝
public native void copyMemory(Object srcBase, long srcOffset, Object destBase, long destOffset, long bytes);
//相当于上面的srcBase和destBase均为null
public native void copyMemory(long srcAddress, long destAddress, long bytes);

//获取对象中偏移地址处的值,忽略修饰限定符的访问限制(还有getBoolean, getChar, getByte, getShort, getInt, getLong, getFloat, getDouble)
public native Object getObject(Object o, long offset);
//设置对象中偏移地址处的值,忽略修饰限定符的访问限制(还有putBoolean, putChar, putByte, putShort, putInt, putLong, putFloat, putDouble)
public native void putObject(Object o, long offset, Object x);

//获取指定地址处的值(还有getBoolean, getChar, getByte, getShort, getInt, getLong, getFloat, getDouble, getAddress)
public native byte getByte(long address);
//设置指定地址处的值(还有putBoolean, putChar, putByte, putShort, putInt, putLong, putFloat, putDouble, putAddress)
public native void putByte(long address, byte x);

知识:堆内内存vs堆外内存

堆内内存:在Java中创建的对象都处于堆内内存(heap)中,堆内内存是由JVM所管控的Java进程内存,并且它们遵循JVM的内存管理机制,JVM会采用垃圾回收机制统一管理堆内存。

堆外内存:与之相对的是堆外内存,存在于JVM管控之外的内存区域,Java中对堆外内存的操作,依赖于Unsafe提供的操作堆外内存的native方法。

使用堆外内存的原因

  • 对垃圾回收停顿的改善。由于堆外内存是直接受操作系统管理而不是JVM,所以当我们使用堆外内存时,即可保持较小的堆内内存规模。从而在GC时减少回收停顿对于应用的影响。
  • 提升程序I/O操作的性能。通常在I/O通信过程中,会存在堆内内存到堆外内存的数据拷贝操作,对于需要频繁进行内存间数据拷贝且生命周期较短的暂存数据,都建议存储到堆外内存。

例子:操作内存

private static void f1(Unsafe unsafe) {
    //分配8字节内存
    long address = unsafe.allocateMemory(8);
    long v1 = unsafe.getLong(address);//不确定值

    //赋值-1
    unsafe.putLong(address, -1);
    long v2 = unsafe.getLong(address);//-1

    //第一个字节的值也是-1
    byte v3 = unsafe.getByte(address);//-1

    //第一个字节赋值为0
    unsafe.putByte(address, (byte) 0);
    //结果还是负数,可见是小端模式(首地址存放低序字节)
    long v4 = unsafe.getLong(address);//-256(= -1 - 255)
}

例子:超大数组

java数组最大尺寸是Integer.MAX_VALUE,我们可以自己分配堆外内存当数组用:

//只能存byte类型
class SuperArray {
    private final static int BYTE = 1;

    private long size;
    private long address;

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

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

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

    public long size() {
        return size;
    }
}

使用:

long SUPER_SIZE = (long)Integer.MAX_VALUE * 2;
SuperArray array = new SuperArray(SUPER_SIZE);
System.out.println("Array size:" + array.size()); // 4294967294
for (int i = 0; i < 100; i++) {
    array.set((long)Integer.MAX_VALUE + i, (byte)3);
    sum += array.get((long)Integer.MAX_VALUE + i);
}
System.out.println("Sum of 100 elements:" + sum);  // 300

应用:判断字节序

小端模式(首地址存放低位字节)

大端模式(首地址存放高位字节)

private static void f2(Unsafe unsafe) {
    long address = unsafe.allocateMemory(2);
    unsafe.putShort(address, (short) 0x0201);

    byte v0 = unsafe.getByte(address);
    byte v1 = unsafe.getByte(address + 1);
    
    if (v0 == 2 && v1 == 1) {
        System.out.println("big-endian");
    } else if (v0 == 1 && v1 == 2) {
        System.out.println("little-endian");
    } else {
        System.out.println("unknown");
    }
}

CAS相关

unsafe提供的接口

CAS相关操作

public native boolean compareAndSwapObject(Object o, long offset, Object expected, Object update);
public native boolean compareAndSwapInt(Object o, long offset, int expected, int update);
public native boolean compareAndSwapLong(Object o, long offset, long expected, long update);

应用:并发类中使用

CAS在java.util.concurrent.atomic相关类、Java AQS、CurrentHashMap等实现上有非常广泛的应用。

class MyAtomicInteger {
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
            //定位到对象中value字段的内存地址
            valueOffset = unsafe.objectFieldOffset(MyAtomicInteger.class.getDeclaredField("value"));
        } catch (NoSuchFieldException e) {
            throw new Error(e);
        }
    }

    private volatile int value;

    public MyAtomicInteger(int value) {
        this.value = value;
    }

    public int get() {
        return value;
    }

    public boolean compareAndSet(int expect, int update) {
        //根据CAS操作实现对value字段的原子操作
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }
}

线程调度

unsafe提供的接口

线程挂起、恢复、锁机制。

//取消线程阻塞(入参为阻塞的线程)
public native void unpark(Object thread);
//阻塞当前线程,超时后被唤醒
//绝对时间:第一个是true,time表示绝对时间,单位毫秒
//相对时间:第一个是false,time表示相对时间,单位纳秒,0表示永不到期
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);

例子:使用park和unpark

import sun.misc.Unsafe;

import java.lang.reflect.Field;

public class Main {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InterruptedException {
        Field f = Unsafe.class.getDeclaredField("theUnsafe");
        f.setAccessible(true);
        Unsafe unsafe = (Unsafe) f.get(null);

        //线程1必须等待唤醒
        Thread thread1 = new Thread(() -> {
            System.out.println("线程1:执行任务");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            }
            System.out.println("线程1:挂起,等待唤醒才能继续执行任务");
            unsafe.park(false, 0);
            System.out.println("线程1:执行完毕");
        });
        thread1.start();

        //线程2必须等待唤醒
        Thread thread2 = new Thread(() -> {
            System.out.println("线程2:执行任务");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            }
            System.out.println("线程2:挂起,等待唤醒才能继续执行任务");
            unsafe.park(false, 0);
            System.out.println("线程2:执行完毕");
        });
        thread2.start();

        Thread.sleep(5000);
        System.out.println("唤醒线程2");
        unsafe.unpark(thread2);
        Thread.sleep(1000);
        System.out.println("唤醒线程1");
        unsafe.unpark(thread1);

        //线程3自动唤醒
        Thread thread3 = new Thread(() -> {
            System.out.println("线程3:执行任务");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            }
            System.out.println("线程3:挂起,等待时间到自动唤醒");
            unsafe.park(false, 3000000000L);
            System.out.println("线程3:执行完毕");
        });
        thread3.start();

        //线程4自动唤醒
        Thread thread4 = new Thread(() -> {
            System.out.println("线程4:执行任务");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            }
            System.out.println("线程4:挂起,等待时间到自动唤醒");
            long time = System.currentTimeMillis() + 3000;
            unsafe.park(true, time);
            System.out.println("线程4:执行完毕");
        });
        thread4.start();
    }
}

输出:

线程1:执行任务
线程2:执行任务
线程1:挂起,等待唤醒才能继续执行任务
线程2:挂起,等待唤醒才能继续执行任务
唤醒线程2
线程2:执行完毕
唤醒线程1
线程1:执行完毕
线程3:执行任务
线程4:执行任务
线程3:挂起,等待时间到自动唤醒
线程4:挂起,等待时间到自动唤醒
线程3:执行完毕
线程4:执行完毕

应用:锁+同步框架

Java锁和同步器框架的核心类AbstractQueuedSynchronizer,就是通过调用LockSupport.park()LockSupport.unpark()实现线程的阻塞和唤醒的,而LockSupport的park、unpark方法实际是调用Unsafe的park、unpark方式来实现。

Class相关

unsafe提供的接口

提供Class和它的静态字段的操作相关方法,包含静态字段内存定位、定义类、定义匿名类、检验&确保初始化等。

//获取静态字段的内存地址偏移量(如果入参非法,则抛出IllegalArgumentException)
public native long staticFieldOffset(Field f);
//获取成员字段的内存地址偏移量(如果入参非法,则抛出IllegalArgumentException)
public native long objectFieldOffset(Field f);
//获取静态字段的对象指针,实际返回值是静态字段所在Class对象的一个内存快照,它的作用只是方便调用像getInt(Object,long)等的任意方法。
public native Object staticFieldBase(Field f);

//判断某个类是否需要初始化(通常返回true)
public native boolean shouldBeInitialized(Class<?> c);
//确保某个类已经初始化
public native void ensureClassInitialized(Class<?> c);

//定义一个类,此方法会跳过JVM的所有安全检查,默认情况下,ClassLoader(类加载器)和ProtectionDomain(保护域)实例来源于调用者
public native Class<?> defineClass(String name, byte[] b, int off, int len, ClassLoader loader, ProtectionDomain protectionDomain);
//定义一个匿名类
public native Class<?> defineAnonymousClass(Class<?> hostClass, byte[] data, Object[] cpPatches);

应用:从Java 8开始,JDK使用invokedynamic及VM Anonymous Class结合来实现Java语言层面上的Lambda表达式。

使用staticFieldBase

获取类中的静态属性值,只依赖到Field的实例,剩余工作交给Unsafe的API。

public class Main2 {

    public static void main(String[] args) throws Exception {
        Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe");
        theUnsafe.setAccessible(true);
        Unsafe unsafe = (Unsafe) theUnsafe.get(null);
        //这里必须预先实例化Person,否则它的静态字段不会加载
        Person person = new Person();
        Class<?> personClass = person.getClass();
        Field name = personClass.getField("NAME");
        //注意,上面的Field实例是通过Class获取的,但是下面的获取静态属性的值没有依赖到Class
        System.out.println(unsafe.getObject(unsafe.staticFieldBase(name), unsafe.staticFieldOffset(name)));
    }
}

public class Person {
    public static String NAME = "doge";
    public String age;
}

对象操作

unsafe提供的接口

对象成员属性相关操作及非常规的对象实例化方式等相关方法。

//返回对象成员属性在内存地址相对于此对象的内存地址的偏移量
public native long objectFieldOffset(Field f);
//获得给定对象的指定地址偏移量的值,与此类似操作还有:getInt,getDouble,getLong,getChar等
public native Object getObject(Object o, long offset);
//给定对象的指定地址偏移量设值,与此类似操作还有:putInt,putDouble,putLong,putChar等
public native void putObject(Object o, long offset, Object x);
//从对象的指定偏移量处获取变量的引用,使用volatile的加载语义
public native Object getObjectVolatile(Object o, long offset);
//存储变量的引用到对象的指定的偏移量处,使用volatile的存储语义
public native void putObjectVolatile(Object o, long offset, Object x);
//有序、延迟版本的putObjectVolatile方法,不保证值的改变被其他线程立即看到。只有在field被volatile修饰符修饰时有效
//只是让JIT不改变指令执行顺序,而putObject()有可能乱序执行
public native void putOrderedObject(Object o, long offset, Object x);
//绕过构造方法、初始化代码来创建对象
public native Object allocateInstance(Class<?> cls) throws InstantiationException;

(1)普通读写
getInt
putInt
(2)volatile读写
保证可见性和有序性,但代价更加昂贵。
getIntVolatile
putIntVolatile
(3)有序写入
只保证写入的有序性,不保证可见性,就是说一个线程的写入不保证其他线程立马可见。
而与volatile写入相比putOrderedXX写入代价相对较低,putOrderedXX写入不保证可见性,但是保证有序性,所谓有序性,就是保证指令不会重排序。

数组相关

unsafe提供的接口

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

应用:AtomicIntegerArray(可以实现对Integer数组中每个元素的原子性操作)

内存屏障

unsafe提供的接口

//屏障前的load不会与屏障后的load或store重排序
public native void loadFence();
//屏障前的store不会与屏障后的load或store重排序
public native void storeFence();
//屏障前的load或store不会与屏障后的load或store重排序
public native void fullFence();

应用:乐观锁StampedLock中,validate(long stamp)校验锁状态时,会通过Unsafe的loadFence方法加入一个load内存屏障,目的是避免StampedLock.validate中锁状态校验运算发生重排序导致锁状态校验不准确的问题。

public boolean validate(long stamp) {
    U.loadFence();
    return (stamp & SBITS) == (state & SBITS);
}

系统相关

unsafe提供的接口

含两个获取系统相关信息的方法。

//返回系统指针的大小。返回值为4(32位系统)或 8(64位系统)。
public native int addressSize();
//内存页的大小,此值为2的幂次方。
public native int pageSize();

Unsafe源码

JAVA中神奇的双刃剑–Unsafe - throwable - 博客园 (cnblogs.com)文章最下面

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值