做网站前必须设计原型吗,成都爱品阅网络科技有限公司,五常市网站,商务类网站前言 几乎每个使用 Java开发的工具、软件基础设施、高性能开发库都在底层使用了sun.misc.Unsafe#xff0c;比如Netty、Cassandra、Hadoop、Kafka等。Unsafe类在提升Java运行效率#xff0c;增强Java语言底层操作能力方面起了很大的作用。但Unsafe类在sun.misc包下#xff0… 前言 几乎每个使用 Java开发的工具、软件基础设施、高性能开发库都在底层使用了sun.misc.Unsafe比如Netty、Cassandra、Hadoop、Kafka等。Unsafe类在提升Java运行效率增强Java语言底层操作能力方面起了很大的作用。但Unsafe类在sun.misc包下不属于Java标准。很早之前在阅读并发编程相关类的源码时看到Unsafe类产生了一个疑惑既然是并发编程中用到的类为什么命名为Unsafe呢深入了解之后才知道这里的Unsafe并不是说线程安全与否而是指该类对于普通的程序员来说是”危险“的一般应用开发者不会也不应该用到此类。因为Unsafe类功能过于强大提供了一些可以绕开JVM的更底层功能。它让Java拥有了像C语言的指针一样操作内存空间的能力能够提升效率但也带来了指针的问题。官方并不建议使用也没提供文档支持甚至计划在高版本中去掉该类。但对于开发者来说了解该类提供的功能更有助于我们学习CAS、并发编程等相关的知识还是非常有必要学习和了解的。Unsafe的构造 Unsafe类是final的不允许继承且构造函数是private使用了单例模式来通过一个静态方法getUnsafe()来获取。private Unsafe() {}CallerSensitivepublic static Unsafe getUnsafe() {Class var0 Reflection.getCallerClass();if (!VM.isSystemDomainLoader(var0.getClassLoader())) {throw new SecurityException(Unsafe);} else {return theUnsafe;}}在getUnsafe方法中对单例模式中的对象创建做了限制如果是普通的调用会抛出一个SecurityException异常。只有由主类加载器加载的类才能调用这个方法。那么如何获得Unsafe类的对象呢通常采用反射机制public static Unsafe getUnsafe() throws IllegalAccessException {Field unsafeField Unsafe.class.getDeclaredFields()[0];unsafeField.setAccessible(true);return (Unsafe) unsafeField.get(null);
}当获得Unsafe对象之后就可以”为所欲为“了。下面就来看看通过Unsafe方法我们可以做些什么。Unsafe的主要功能 可先从根据下图从整体上了解一下Unsafe提供的功能Unsafe功能概述下面挑选重要的功能进行讲解。一、内存管理 Unsafe的内存管理功能主要包括普通读写、volatile读写、有序写入、直接操作内存等分配内存与释放内存的功能。普通读写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中有广泛的应用ConcurrentHashMapConcurrentLinkedQueue中都有用到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类的主要目的大多数情况下是为了提升运行效率、增强功能。但同时也面临着出错、内存管理等风险。只有深入了解且有必要的情况下才建议使用。往期推荐synchronized底层是如何实现的线程休眠竟然有 5 种方法虾皮二面什么是零拷贝?如何实现零拷贝