Unsafe类,你知道多少呢?

java-教程王 Java教程 发布时间:2022-04-11 11:38:20 阅读数:13039 1
在一些常用的中间件中,我们经常看见Unsafe类(sun.misc.Unsafe)的使用,如Netty、Cassandra、Hadoop、Kafka等,那么你知道Unsafe类的功能吗?
下文笔者将一一道来,如下所示:

Unsafe类的功能简介

Unsafe类在sun.misc包下
Unsafe类不是Java标准类,一般的开发者不会涉及此类的开发
Unsafe类可提高java的运行效率

Unsafe类的功能:
     使我们可跳过JVM,使java语言拥有c语言指针一样的能力,
	  如:操作内存空间,CAS,并发编程等能力

Unsafe类简介

Unsafe类使用“final”修改,不可以继承,并且构造函数是private,所以我们只能使用静态方法获取它的实例
例:
 private Unsafe() {
    }
​
@CallerSensitive
public static Unsafe getUnsafe() {
  Class var0 = Reflection.getCallerClass();
  if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
         throw new SecurityException("Unsafe");
        } else {
            return theUnsafe;
        }
    }
从getUnsafe()方法中,我们可以得知,
只有主类加载器才可以调用此方法,否则就会抛出异常
例:调用getUnsafe方法返回Unsafe
public static Unsafe getUnsafe() throws IllegalAccessException {
    Field unsafeField = Unsafe.class.getDeclaredFields()[0];
    unsafeField.setAccessible(true);
    return (Unsafe) unsafeField.get(null);
}

Unsafe的主要功能

Unsafe类

内存管理

Unsafe的内存管理功能主要包括
   普通读写、volatile读写、有序写入、直接操作内存等分配内存与释放内存的功能
普通读写
Unsafe可以读写一个类的属性
即使这个属性是私有的
也可以对这个属性进行读写。
//获取内存地址指向的整数
public native int getInt(Object var1, long var2);
       getInt用于从对象的指定偏移地址处读取一个int
​
//将整数写入指定内存地址
public native void putInt(Object var1, long var2, int var4);
      putInt用于在对象指定偏移地址处写入一个int。其他原始类型也提供有对应的方法

另:Unsafe的getByte、putByte方法提供了直接在一个地址上进行读写的功能

volatile读写
  普通的读写无法保证可见性和有序性,而volatile读写就可以保证可见性和有序性。
// 获取内存地址指向的整数,并支持volatile语义
public native int getIntVolatile(Object var1, long var2);
​
// 将整数写入指定内存地址,并支持volatile语义
public native void putIntVolatile(Object var1, long var2, int var4);
   volatile读写要保证可见性和有序性,相对普通读写更加昂贵

有序写入
   有序写入只保证写入的有序性,不保证可见性
   就是说一个线程的写入不保证其他线程立马可见。
// 将整数写入指定内存地址、有序或者有延迟的方法
public native void putOrderedInt(Object var1, long var2, int var4);

而与volatile写入相比putOrderedXX写入代价相对较低
putOrderedXX写入不保证可见性但
是保证有序性,所谓有序性,就是保证指令不会重排序。

直接操作内存
Unsafe提供了直接操作内存的能力:
// 分配内存
public native long allocateMemory(long var1);
// 重新分配内存
public native long reallocateMemory(long var1, long var3);
// 内存初始化
public native void setMemory(long var1, long var3, byte var5);
// 内存复制
public native void copyMemory(Object var1, long var2, Object var4, long var5, long var7);
// 清除内存
public native void freeMemory(long var1);
​
对应操作内存,也提供了一些获取内存信息的方法:
// 获取内存地址
public native long getAddress(long var1);
​
public native int addressSize();
​
public native int pageSize();

注意事项:
 使用copyMemory方法可以实现一个通用的对象拷贝方法
  无需再对每一个对象都实现clone方法,但只能做到对象浅拷贝

"非常规”对象实例化

通常情况下,我们使用new关键字实例化一个对象,但是Unsafe类中有一个方法allocateInstance,可直接生成对象实例,无需构造方法和其它初始化方法
// 直接生成对象实例,不会调用这个实例的构造方法
public native Object allocateInstance(Class<?> var1) throws InstantiationException;

类加载

通过以下方法,可以实现类的定义、创建等操作。
// 方法定义一个类,用于动态地创建类
public native Class<?> defineClass(String var1, byte[] var2, int var3, int var4, ClassLoader var5, ProtectionDomain var6);

//  动态的创建一个匿名内部类
public native Class<?> defineAnonymousClass(Class<?> var1, byte[] var2, Object[] var3);

// 判断是否需要初始化一个类
public native boolean shouldBeInitialized(Class<?> var1);

// 保证已经初始化过一个类
public native void ensureClassInitialized(Class<?> var1);

偏移量相关

Unsafe提供以下方法获取对象的指针,通过对指针进行偏移,不仅可以直接修改指针指向的数据(即使它们是私有的),甚至可以找到JVM已经认定为垃圾、可以进行回收的对象。
// 获取静态属性Field在对象中的偏移量,读写静态属性时必须获取其偏移量
public native long staticFieldOffset(Field var1);
// 获取非静态属性Field在对象实例中的偏移量,读写对象的非静态属性时会用到这个偏移量
public native long objectFieldOffset(Field var1);
// 返回Field所在的对象
public native Object staticFieldBase(Field var1);
// 返回数组中第一个元素实际地址相对整个数组对象的地址的偏移量
public native int arrayBaseOffset(Class<?> var1);
// 计算数组中第一个元素所占用的内存空间
public native int arrayIndexScale(Class<?> var1);

数组操作

数组操作提供了以下方法:
// 获取数组第一个元素的偏移地址
public native int arrayBaseOffset(Class<?> var1);
// 获取数组中元素的增量地址
public native int arrayIndexScale(Class<?> var1);

线程调度

线程调度相关方法如下:
// 唤醒线程
public native void unpark(Object var1);
// 挂起线程
public native void park(boolean var1, long var2);
// 用于加锁,已废弃
public native void monitorEnter(Object var1);
// 用于加锁,已废弃
public native void monitorExit(Object var1);
// 用于加锁,已废弃
public native boolean tryMonitorEnter(Object var1);

使用park方法将线程进行挂起, 线程将一直阻塞到超时或中断条件出现
使用unpark方法可以终止一个挂起的线程,使其恢复正常

CAS操作

Unsafe类的CAS操作可能是使用最多的方法
它为Java的锁机制提供了一种新的解决办法,
如AtomicInteger等类都是通过该方法来实现的
compareAndSwap方法是原子的,可以避免繁重的锁机制,提高代码效率

public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

CAS一般用于乐观锁
它在Java中有广泛的应用,ConcurrentHashMap,ConcurrentLinkedQueue中都有用到CAS来实现乐观锁。

内存屏障

JDK8新引入了用于定义内存屏障、避免代码重排的方法:
// 保证在这个屏障之前的所有读操作都已经完成
public native void loadFence();

// 保证在这个屏障之前的所有写操作都已经完成
public native void storeFence();

// 保证在这个屏障之前的所有读写操作都已经完成
public native void fullFence();

其他

当然,Unsafe类中还提供了大量其他的方法
 如上面提到的CAS操作,以AtomicInteger为例
  当我们调用getAndIncrement、getAndDecrement等方法时
  本质上调用的就是Unsafe的getAndAddInt方法

public final int getAndIncrement() {
    return unsafe.getAndAddInt(this, valueOffset, 1);
}

public final int getAndDecrement() {
    return unsafe.getAndAddInt(this, valueOffset, -1);
}
版权声明

本文仅代表作者观点,不代表本站立场。
本文系作者授权发表,未经许可,不得转载。

本文链接: https://www.Java265.com/JavaCourse/202204/2835.html

最近发表

热门文章

好文推荐

Java265.com

https://www.java265.com

站长统计|粤ICP备14097017号-3

Powered By Java265.com信息维护小组

使用手机扫描二维码

关注我们看更多资讯

java爱好者