¿Qué tan sorprendente es la clase Unsafe utilizada por los principales marcos?

prefacio

Casi todas las herramientas, infraestructuras de software y bibliotecas de desarrollo de alto rendimiento desarrolladas en Java utilizan sun.misc.Unsafe en la parte inferior, como Netty, Cassandra, Hadoop, Kafka, etc.

La clase Unsafe ha jugado un papel importante en la mejora de la eficiencia operativa de Java y en la mejora de las capacidades operativas subyacentes del lenguaje Java. Pero la clase Unsafe está bajo el paquete sun.misc y no pertenece al estándar Java.

Hace mucho tiempo, cuando leí el código fuente de las clases relacionadas con la programación concurrente, vi la clase Unsafe y tuve una duda: Como es una clase que se usa en programación concurrente, ¿por qué se llama Unsafe?

Después de una comprensión profunda, sé que Inseguro aquí no significa seguridad de subprocesos o no, sino que se refiere a: esta clase es "peligrosa" para los programadores comunes, y los desarrolladores de aplicaciones generales no usarán ni deberían usar esta clase.

Debido a que la clase Unsafe es demasiado poderosa, proporciona algunas funciones de nivel inferior que pueden pasar por alto la JVM. Permite que Java tenga la capacidad de operar el espacio de la memoria como punteros en lenguaje C, lo que puede mejorar la eficiencia, pero también trae el problema de los punteros. No se recomienda su uso oficialmente, ni proporciona soporte de documentación, e incluso planea eliminar esta clase en versiones altas.

Pero para los desarrolladores, comprender las funciones proporcionadas por esta clase es más útil para aprender CAS, programación concurrente y otros conocimientos relacionados, y aún es muy necesario aprender y comprender.

Construcción de Inseguro

La clase Unsafe es "final", la herencia no está permitida y el constructor es privado, utilizando el patrón singleton para obtenerlo a través de un método estático getUnsafe().

    private Unsafe() {
    }
    @CallerSensitive
    public static Unsafe getUnsafe() {
        Class var0 = Reflection.getCallerClass();
        if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
            throw new SecurityException("Unsafe");
        } else {
            return theUnsafe;
        }
    }
复制代码

La creación de objetos en modo singleton está restringida en el método getUnsafe, si es una llamada normal, se lanzará una SecurityException. Solo las clases cargadas por el cargador de clases principal pueden llamar a este método.

Entonces, ¿cómo obtener el objeto de la clase Unsafe? Por lo general, se utiliza el mecanismo de reflexión:

public static Unsafe getUnsafe() throws IllegalAccessException {
    Field unsafeField = Unsafe.class.getDeclaredFields()[0];
    unsafeField.setAccessible(true);
    return (Unsafe) unsafeField.get(null);
}
复制代码

Cuando obtiene el objeto Inseguro, puede "hacer lo que quiera". Echemos un vistazo a lo que podemos hacer con el método Inseguro.

Características clave de inseguro

Primero puede comprender las funciones proporcionadas por Unsafe en su conjunto de acuerdo con la siguiente figura:

Unsafe功能概述

A continuación se seleccionan funciones importantes para explicar.

1. Gestión de la memoria

Las funciones de administración de memoria de Unsafe incluyen principalmente: lectura y escritura ordinaria, lectura y escritura volátil, escritura ordenada, manipulación directa de memoria y otras funciones de asignación y liberación de memoria.

Lectura y escritura ordinaria.

Unsafe可以读写一个类的属性,即便这个属性是私有的,也可以对这个属性进行读写。

// 获取内存地址指向的整数
public native int getInt(Object var1, long var2);
// 将整数写入指定内存地址
public native void putInt(Object var1, long var2, int var4);
复制代码

getInt用于从对象的指定偏移地址处读取一个int。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方法,可以直接生成对象实例,且无需调用构造方法和其他初始化方法。

这在对象反序列化的时候会很有用,能够重建和设置final字段,而不需要调用构造方法。

// 直接生成对象实例,不会调用这个实例的构造方法
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);
复制代码

arrayBaseOffset与arrayIndexScale配合起来使用,就可以定位数组中每个元素在内存中的位置。

由于Java的数组最大值为Integer.MAX_VALUE,使用Unsafe类的内存分配方法可以实现超大数组。实际上这样的数据就可以认为是C数组,因此需要注意在合适的时间释放内存。

六、线程调度

线程调度相关方法如下:

// 唤醒线程
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方法可以终止一个挂起的线程,使其恢复正常。

整个并发框架中对线程的挂起操作被封装在LockSupport类中,LockSupport类中有各种版本pack方法,但最终都调用了Unsafe.park()方法。

七、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);
}
复制代码

在实践的过程中,如果阅读其他框架或类库实现,当发现用到Unsafe类,可对照该类的整体功能,结合应用场景进行分析,即可大概了解其功能。

小结

经过本文的分析,想必大家在阅读源码时,再遇到Unsafe类的调用,一定大概猜出它是用来干什么的。使用Unsafe类的主要目的大多数情况下是为了提升运行效率、增强功能。但同时也面临着出错、内存管理等风险。只有深入了解,且有必要的情况下才建议使用。

博主简介:《SpringBoot技术内幕》技术图书作者,酷爱钻研技术,写技术干货文章。

公众号:「程序新视界」,博主的公众号,欢迎关注~

技术交流:请联系博主微信号:zhuan2quan

Supongo que te gusta

Origin juejin.im/post/7080136599408738335
Recomendado
Clasificación