好文笔记——unsafe类

本文详细解读了Java中的Unsafe类,涉及内存操作、CAS操作、线程调度、Class相关操作、对象和数组处理,以及内存屏障和系统底层细节。通过实例展示了Unsafe在高性能场景和并发控制中的应用,如DirectByteBuffer和锁同步机制。
摘要由CSDN通过智能技术生成

目录

unsafe类整体功能

1.1内存操作

1.2 CAS 

1.3线程调度

1.4Class相关

1.5 对象操作

1.6 数组相关

1.7 内存屏障

1.8系统相关


先把原文链接放上,我这只是自己的笔记Java魔法类:Unsafe应用解析 - 美团技术团队

unsafe类整体功能

1.1内存操作

//分配内存, 相当于C++的malloc函数
public native long allocateMemory(long bytes);
//扩充内存
public native long reallocateMemory(long address, long bytes);
//释放内存
public native void freeMemory(long address);
//在给定的内存块中设置值
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);
//获取给定地址值,忽略修饰限定符的访问限制。与此类似操作还有: 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);
//获取给定地址的byte类型的值(当且仅当该内存地址为allocateMemory分配时,此方法结果为确定的)
public native byte getByte(long address);
//为给定地址设置byte类型的值(当且仅当该内存地址为allocateMemory分配时,此方法结果才是确定的)
public native void putByte(long address, byte x);

堆内内存交由JVM管理,堆外内存可以用unsafe类操作

优势:(1)手动分配和释放堆外内存,减少GC (2)I/O通信,提升堆内内存到堆外内存拷贝数据的性能

典型应用:DirectByteBuffer

1.2 CAS 

/**
	*  CAS
  * @param o         包含要修改field的对象
  * @param offset    对象中某field的偏移量
  * @param expected  期望值
  * @param update    更新值
  * @return          true | false
  */
public final native boolean compareAndSwapObject(Object o, long offset,  Object expected, Object update);

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

1.3线程调度


//取消阻塞线程
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);

如上源码说明中,方法park、unpark即可实现线程的挂起与恢复,将一个线程进行挂起是通过park方法实现的,调用park方法后,线程将一直阻塞直到超时或者中断等条件出现;unpark可以终止一个挂起的线程,使其恢复正常。

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

1.4Class相关

//获取给定静态字段的内存地址偏移量,这个值对于给定的字段是唯一且固定不变的
public native long staticFieldOffset(Field f);
//获取一个静态类中给定字段的对象指针
public native Object staticFieldBase(Field f);
//判断是否需要初始化一个类,通常在获取一个类的静态属性的时候(因为一个类如果没初始化,它的静态属性也不会初始化)使用。 当且仅当ensureClassInitialized方法不生效时返回false。
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  Lambda表达式与defineAnonymousClass

1.5 对象操作

//返回对象成员属性在内存地址相对于此对象的内存地址的偏移量
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修饰符修饰时有效
public native void putOrderedObject(Object o, long offset, Object x);
//绕过构造方法、初始化代码来创建对象
public native Object allocateInstance(Class<?> cls) throws InstantiationException;

典型应用:Locksupport的park方法除了park当前线程,也把当前线程放在指定偏移量里,调用了putObject方法,allocateInstance绕过构造方法,直接实例化对象  ,应用再反序列化中。 

1.6 数组相关

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

典型应用:这两个与数据操作相关的方法,在java.util.concurrent.atomic 包下的AtomicIntegerArray(可以实现对Integer数组中每个元素的原子性操作)中有典型的应用,如下图AtomicIntegerArray源码所示,通过Unsafe的arrayBaseOffset、arrayIndexScale分别获取数组首元素的偏移地址base及单个元素大小因子scale。后续相关原子性操作,均依赖于这两个值进行数组中元素的定位,如下图二所示的getAndAdd方法即通过checkedByteOffset方法获取某数组元素的偏移地址,而后通过CAS实现原子性操作。

1.7 内存屏障

//内存屏障,禁止load操作重排序。屏障前的load操作不能被重排序到屏障后,屏障后的load操作不能被重排序到屏障前
public native void loadFence();
//内存屏障,禁止store操作重排序。屏障前的store操作不能被重排序到屏障后,屏障后的store操作不能被重排序到屏障前
public native void storeFence();
//内存屏障,禁止load、store操作重排序
public native void fullFence();

 典型应用:StampedLock的validate方法检验stamp中,就是利用读屏障,确保当前读的所有数据都为当前时刻最新且之后的读操作不会被排序到该读操作之前,之后利用stamp的按位与操作完成数据比对

1.8系统相关

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
对于unsafe加载和卸载,需要先了解一些基本概念。 Java中,加载分为三个阶段:加载、链接、初始化。其中,链接阶段又分为验证、准备、解析三个阶段。 - 加载:查找并加载的二进制数据。 - 链接: - 验证:验证被加载的二进制数据的正确性。 - 准备:为变量(static修饰的变量)分配内存并初始化为默认值。 - 解析:把中的符号引用转换为直接引用。 - 初始化:为的静态变量赋值,执行静态初始化块。 Java中,卸载因为JVM设计的限制,是不可控的。只有当一个的所有实例都被回收,并且它的ClassLoader被回收时,才会尝试卸载。即一个的ClassLoader被卸载时,才会尝试卸载这个。 下面,我们将结合Unsafe来进行加载和卸载操作。 1. 使用Unsafe加载 使用Unsafe,可以通过内存地址手动加载。下面是一个Unsafe加载的例子,在该例子中,首先使用URLClassLoader从指定路径加载TestClass.class,然后获取TestClass.class在内存中的地址,通过Unsafe手动加载TestClass.class。 ```java // 加载TestClass.class URL url = file.toURI().toURL(); URLClassLoader classLoader = new URLClassLoader(new URL[]{url}, null); Class<?> clazz = classLoader.loadClass("TestClass"); // 获取TestClass在内存中的地址 Field classField = Unsafe.class.getDeclaredField("theUnsafe"); classField.setAccessible(true); Unsafe unsafe = (Unsafe) classField.get(null); long classAddress = unsafe.staticFieldOffset(clazz.getDeclaredField("class$0")); // 使用Unsafe加载TestClass.class Class<?> loadedClass = (Class<?>) unsafe.getObject(null, classAddress); ``` 2. 使用Unsafe卸载 由于Java中对卸载的限制,使用Unsafe卸载是一件非常危险的操作,需要谨慎使用。下面是一个使用Unsafe卸载的例子,在该例子中,首先使用Unsafe手动加载TestClass.class,然后使用Unsafe卸载TestClass。 ```java // 使用Unsafe加载TestClass.class byte[] classData = getClassData(); Class<?> loadedClass = unsafe.defineClass(null, classData, 0, classData.length, null, null); // 使用Unsafe卸载TestClass.class Field classLoaderField = ClassLoader.class.getDeclaredField("classes"); classLoaderField.setAccessible(true); Vector<Class<?>> classes = (Vector<Class<?>>) classLoaderField.get(classLoader); classes.remove(loadedClass); Field classLoaderValueField = ClassLoader.class.getDeclaredField("classLoaderValues"); classLoaderValueField.setAccessible(true); Hashtable<Class<?>, Object> classLoaderValues = (Hashtable<Class<?>, Object>) classLoaderValueField.get(classLoader); classLoaderValues.remove(loadedClass); Class<?> classLoaderClass = Class.forName("java.lang.ClassLoader"); Method unregisterMethod = classLoaderClass.getDeclaredMethod("unregister", Class.class); unregisterMethod.setAccessible(true); unregisterMethod.invoke(classLoader, loadedClass); ``` 总的来说,在Java中手动加载和卸载是一件非常危险的操作,需要谨慎使用,除非是在极端情况下才需要考虑使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值