Unsafe概述

news2024/11/18 11:50:55

前言

​ 如果看过Java并发包里面的源码、或者netty网络通讯相关源码,就会到Unsafe类有一定的了解。这个与其的特性有关。

介绍

​ **Unsafe是位于sun.misc包下的一个类,主要提供一些用于执行低级别、不安全操作的方法,如直接访问系统内存资源、自主管理内存资源等,这些方法在提升Java运行效率、增强Java语言底层资源操作能力方面起到了很大的作用。**但是过度使用、不正确使用会适得其反,所以慎用。

1、基本使用

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

​ 所以如果要获取实例,有以下这两种方法:

1)设置调用方法类被引导类加载器加载。

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

java -Xbootclasspath/a: ${path}   // 其中path为调用Unsafe相关方法的类所在jar包路径 
2)通过反射获取单例对象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功能介绍

​ **Unsafe提供的API大致可分为内存操作、CAS、Class相关、对象操作、线程调度、系统信息获取、内存屏障、数组操作等几类。**通过其所提供的服务,就发现很多地方都用到。

img

1、操作内存

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

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

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

API接口
//分配内存, 相当于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);
使用堆外内存的原因

1)对垃圾回收停顿的改善。

​ 由于堆外内存是直接受操作系统管理而不是JVM,所以当我们使用堆外内存时,即可保持较小的堆内内存规模。从而在GC时减少回收停顿对于应用的影响。

2)提升程序I/O操作的性能。

通常在I/O通信过程中,会存在堆内内存到堆外内存的数据拷贝操作,对于需要频繁进行内存间数据拷贝且生命周期较短的暂存数据,都建议存储到堆外内存。

应用

DirectByteBuffer是Java用于实现堆外内存的一个重要类,通常用在通信过程中做缓冲池,如在Netty、MINA等NIO框架中应用广泛。DirectByteBuffer对于堆外内存的创建、使用、销毁等逻辑均由Unsafe提供的堆外内存API来实现。

​ 创建DirectByteBuffer的时候,通过Unsafe.allocateMemory分配内存、Unsafe.setMemory进行内存初始化,而后构建Cleaner对象用于跟踪DirectByteBuffer对象的垃圾回收,以实现当DirectByteBuffer被垃圾回收时,分配的堆外内存一起被释放。

垃圾回收追踪对象Cleaner实现堆外内存释放

虚引用主要用来跟踪对象被垃圾回收的活动。需要使用ReferenceQueue类和PhantomReference类来实现假性通知。Cleaner继承自PhantomReference,用来实现虚引用关联对象被垃圾回收时能够进行系统通知、资源清理等功能。

img

​ 所以当某个被Cleaner引用的对象将被回收时,JVM垃圾收集器会将此对象的引用放入到对象引用中的pending链表中,等待Reference-Handler进行相关处理。其中,Reference-Handler为一个拥有最高优先级的守护线程,会循环不断的处理pending链表中的对象引用,执行Cleaner的clean方法进行相关清理工作。

因此,无法通过虚引用获取与之关联的对象实例(堆外内存),且当对象仅被虚引用引用时,在任何发生GC的时候,其均可被回收。

内存简易结构图

在这里插入图片描述

2、CAS操作

​ **CAS即比较并替换,实现并发算法时常用到的一种技术。CAS操作包含三个操作数——内存位置、预期原值及新值。**执行CAS操作的时候,将内存位置的值与预期原值比较,如果相匹配,那么处理器会自动将该位置值更新为新值,否则,处理器不做任何操作。

说根本,CAS是一条CPU的原子指令(cmpxchg指令),不会造成所谓的数据不一致问题,Unsafe提供的CAS方法(如compareAndSwapXXX)底层实现即为CPU指令cmpxchg。

API接口
/**
	*  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);
应用

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

​ **AtomicInteger的实现中,静态字段valueOffset即为字段value的内存偏移地址,valueOffset的值在AtomicInteger初始化时,在静态代码块中通过Unsafe的objectFieldOffset方法获取。**在AtomicInteger中提供的线程安全方法中,通过字段valueOffset的值可以定位到AtomicInteger对象中value的内存地址,从而可以根据CAS实现对value字段的原子操作。

3、线程调度

包括线程挂起、恢复、Lock的锁机制等方法。

API接口
//取消阻塞线程
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方式来实现。

4、Class相关

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

API接口
//获取给定静态字段的内存地址偏移量,这个值对于给定的字段是唯一且固定不变的
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);
源码分析
//1、注册native方法,是的Unsafe类可以操作C语言 
 private static native void registerNatives(); 
 static { 
     registerNatives(); 
     sun.reflect.Reflection.registerMethodsToFilter(Unsafe.class, "getUnsafe"); 
 } 
 //2、构造方法 
 private Unsafe() {} 
 //3、初始化方法 
 private static final Unsafe theUnsafe = new Unsafe(); 
 //4、初始化方法实现 
 @CallerSensitive 
 public static Unsafe getUnsafe() { 
     Class<?> caller = Reflection.getCallerClass(); 
     if (!VM.isSystemDomainLoader(caller.getClassLoader())) 
         throw new SecurityException("Unsafe"); 
     return theUnsafe; 
 } 

应用

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

​ 在Lambda表达式实现中,通过invokedynamic指令调用引导方法生成调用点,在此过程中,会通过ASM动态生成字节码,而后利用Unsafe的defineAnonymousClass方法定义实现相应的函数式接口的匿名类,然后再实例化此匿名类,并返回与此匿名类中函数式方法的方法句柄关联的调用点;而后可以通过此调用点实现调用相应Lambda表达式定义逻辑的功能。

invokedynamic: invokedynamic是Java 7为了实现在JVM上运行动态语言而引入的一条新的虚拟机指令,它可以实现在运行期动态解析出调用点限定符所引用的方法,然后再执行该方法,invokedynamic指令的分派逻辑是由用户设定的引导方法决定。

VM Anonymous Class:可以看做是一种模板机制,针对于程序动态生成很多结构相同、仅若干常量不同的类时,可以先创建包含常量占位符的模板类,而后通过Unsafe.defineAnonymousClass方法定义具体类时填充模板的占位符生成具体的匿名类。生成的匿名类不显式挂在任何ClassLoader下面,只要当该类没有存在的实例对象、且没有强引用来引用该类的Class对象时,该类就会被GC回收。故而VM Anonymous Class相比于Java语言层面的匿名内部类无需通过ClassClassLoader进行类加载且更易回收。

5、对象操作

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

API接口
//返回对象成员属性在内存地址相对于此对象的内存地址的偏移量
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;
应用

​ **Unsafe中提供allocateInstance方法,仅通过Class对象就可以创建此类的实例对象,而且不需要调用其构造函数、初始化代码、JVM安全检查等。**它抑制修饰符检测,也就是即使构造器是private修饰的也能通过此方法实例化,只需提类对象即可创建相应的对象。

由于这种特性,allocateInstance在java.lang.invoke、Objenesis(提供绕过类构造器的对象生成方式)、Gson(反序列化时用到)中都有相应的应用。

6、数组相关

​ 主要是用于定位数组中每个元素在内存中的位置,通过arrayBaseOffset与arrayIndexScale这两个方法。

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

​ 在java.util.concurrent.atomic 包下的AtomicIntegerArray(可以实现对Integer数组中每个元素的原子性操作)中有典型的应用。

通过Unsafe的arrayBaseOffset、arrayIndexScale分别获取数组首元素的偏移地址base及单个元素大小因子scale。后续相关原子性操作,均依赖于这两个值进行数组中元素的定位。其中getAndAdd方法即通过checkedByteOffset方法获取某数组元素的偏移地址,而后通过CAS实现原子性操作。

7、内存屏障

在Java 8中引入,用于定义内存屏障(也称内存栅栏,内存栅障,屏障指令等,是一类同步屏障指令,是CPU或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作),避免代码重排序。

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

​ 在Java 8中引入了一种锁的新机制——StampedLock,它可以看成是读写锁的一个改进版本。StampedLock提供了一种乐观读锁的实现,这种乐观读锁类似于无锁的操作,完全不会阻塞写线程获取写锁,从而缓解读多写少时写线程“饥饿”现象。

由于StampedLock提供的乐观读锁不阻塞写线程获取读锁,当线程共享变量从主内存load到线程工作内存时,会存在锁状态校验不准确。所以在校验逻辑之前,会通过Unsafe的loadFence方法加入一个load内存屏障,避免因为重排序而导致数据的不准确型。

8、系统相关

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

主要是为java.nio下的工具类Bits中计算待申请内存所需内存页数量的静态方法,其依赖于Unsafe中pageSize方法获取系统内存页大小实现后续计算逻辑。

示例

public class UnsafeTest { 
    public static void main(String[] args) throws Exception { 
        //这里的theUnsafe就是我们源码中的那个theUnsafe 
        Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe"); 
        theUnsafe.setAccessible(true); 
        Unsafe unsafe = (Unsafe) theUnsafe.get(null); 
 
        //1、创建对象实例 
        Author author = (Author) unsafe.allocateInstance(Author.class); 
        //2、操作对象的属性 
        Field ageField = Author.class.getDeclaredField("age"); 
        long fieldOffset = unsafe.objectFieldOffset(ageField); 
        //3、操作数组 
        String[] strings = new String[]{"1", "2", "3"}; 
        long i = unsafe.arrayBaseOffset(String[].class); 
        //4、操作内存 
        long address = unsafe.allocateMemory(8L);         
    } 
} 

补充说明

​ 首先在Oracle的Jdk8无法获取到sun.misc包的源码,想看此包的源码可以直接下载openjdk。在IDEA中windows->preference->installed jres->选中jre->edit->rt.jar->source attachment->external folders->openjdk源码路径。此时就可以查看我们的Unsafe类的源码了。

总结

​ Unsafe功能强大,用法广泛,但是若使用不当,会对程序带来许多不可控的灾难。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1066423.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《DevOps 精要:业务视角》- 读书笔记(三)

DevOps 精要:业务视角&#xff08;三&#xff09; 第3章 原则3.1 价值流3.2 部署流水线3.3 一切都应存储在版本控制系统中3.4 自动化配置管理3.5 完成的定义3.6 小结 第3章 原则 将原则从实践中分离出来&#xff0c;这是一种很有用的做法。当然了&#xff0c;这两个词分别有着…

CCF中国开源大会专访|毛晓光:“联合”是开源走向“共赢”的必由之路

受访嘉宾 | 毛晓光 记者 | 朱珂欣 2023 CCF 中国开源大会&#xff08; CCF ChinaOSC &#xff09;拟于 2023 年 10 月 21 日至 22 日在湖南省长沙市北辰国际会议中心召开。 作为第二届 CCF 中国开源大会&#xff0c;本届大会将组织特邀报告、高峰论坛和领域分论坛等不同类…

防御安全第四次作业

1. 什么是APT&#xff1f; APT全称&#xff1a;Advanced Persistent Threat 高级可持续威胁攻击。 指的是某组织对特定对象展开持续有效的攻击活动。 这种攻击活动具有极强的隐蔽性和针对性&#xff0c;通常会运用受感染的各种介质&#xff0c;供应链和社会工程学等手段&#x…

Kafka 架构

正文 一、Kafka的架构 如上图所示&#xff0c;一个典型的Kafka集群中包含若干Producer&#xff08;可以是web前端产生的Page View&#xff0c;或者是服务器日志&#xff0c;系统CPU、Memory等&#xff09;&#xff0c;若干broker&#xff08;Kafka支持水平扩展&#xff0c;一般…

解决 Jenkins 性能缓慢的问题~转

解决 Jenkins 性能缓慢的问题 Docker中文社区 ​​ 计算机技术与软件专业技术资格持证人 2 人赞同了该文章 没有什么比缓慢的持续集成系统更令人沮丧的了。它减慢了反馈循环并阻止代码快速投入生产。虽然像使用性能更好的服务器可以为您争取时间&#xff0c;但您最终必须投资…

AdaBoost(上):数据分析 | 数据挖掘 | 十大算法之一

⭐️⭐️⭐️⭐️⭐️欢迎来到我的博客⭐️⭐️⭐️⭐️⭐️ &#x1f434;作者&#xff1a;秋无之地 &#x1f434;简介&#xff1a;CSDN爬虫、后端、大数据领域创作者。目前从事python爬虫、后端和大数据等相关工作&#xff0c;主要擅长领域有&#xff1a;爬虫、后端、大数据…

张量-复数操作函数

tf.complex(real,imag,name None),该函数用于将两实数转化为复数。 示例代码如下: import tensorflow.compat.v1 as tf tf.disable_v2_behavior()real tf.constant([2.25,3.25],dtype tf.float64) imag tf.constant([4.75,5.75],dtype tf.float64)with tf.Session() as …

Bigemap中如何添加21级的影像图

工具 Bigemap gis office地图软件 BIGEMAP GIS Office-全能版 Bigemap APP_卫星地图APP_高清卫星地图APP 很多人在咨询如何查看21级的影像图 这里先来说一下影像的像素分辨率&#xff0c;一般17级是1米的、18级是0.5米、19级是0.25米以此类推&#xff0c;咱们国家测绘要求允…

HTTP/2和HTTP/3简介(上)(下)【中科大-郑烇老师】

文章目录 我的总结&#xff1a;HTTP 1.0HTTP 1.0 支持 多个并行连接HTTP 1.1 持久连接&#xff08;非流水线和流水线&#xff09; HTTP/2HTTP /3HTTP的演化 from &#xff1a; https://www.bilibili.com/video/BV1R34y1G76h/?spm_id_from333.788&vd_source21cce77bb69d40a…

Zookeeper经典应用场景实战(二)

文章目录 1、 Zookeeper 分布式锁实战1.1、 什么是分布式锁1.2、 基于数据库设计思路1.3、 基于Zookeeper设计思路一1.4、 基于Zookeeper设计思路二 1、 Zookeeper 分布式锁实战 1.1、 什么是分布式锁 在单体的应用开发场景中涉及并发同步的时候&#xff0c;大家往往采用Sync…

Bootstrap对段落和文本的设置(与段落-和文本相关的类)

目录 01-利用lead类突出显示段落文本02-为段落添加强调样式03-给段落文字添加鼠标停留时的说明语04-添加引用信息05-文本对齐(左对齐、居中对齐、右对齐、两端对齐)06-阻止文本换行07-以省略号显示溢出的文本内容08-转换文本内容中字母的大小写09-设置文本字体的粗细和斜体10-取…

The Sandbox 与 TB Media Global 达成合作

这家总部位于泰国的公司以开发原创、适合家庭观看的内容而闻名&#xff0c;将在 The Sandbox 中推出新的虚拟体验。 备受赞誉的 IP 开发公司 T&B Media Global 正式加入 The Sandbox 元宇宙。该公司以专注于故事讲述艺术而闻名。这次合作不仅为虚拟世界带来了新的维度&…

java常见API----indexof

API indexof API toUpperCase()用法及实例&#xff1a; package daysreplace;import java.util.Locale;public class IndexofApiTest {public static void main(String[] args) {String str "nanyanghhhhh";String bigStr "KKKKAAAAHHHH";System.…

4.物联网射频识别,RFID开发【智能门禁项目】

补充&#xff1a;学习路径 一。项目介绍及需求分析 1.酒店智能门禁使用场景介绍 1.客人入住 客人在前台办理入住手续&#xff0c;前台管理员通过门禁管理系统为客户开一张门禁卡 客户持卡到相应客房&#xff0c;用IC 卡刷卡开门 客人过了入住时间后&#xff0c;卡自动失效&a…

第十课 贪心

文章目录 第十课 贪心lc 322.零钱兑换--中等题目描述代码展示 lc860.柠檬水找零--简单题目描述代码展示 lc455.分发饼干--简单题目描述代码展示 lc122.买卖股票的最佳时机II--中等题目描述代码展示 lc45.跳跃游戏II--中等题目描述代码展示 lc1665.完成所有任务的最少初始能量--…

WPF中样式静态、动态资源、资源字典

1、绑定静态资源后&#xff0c;样式不会发生改变 绑定动态资源后&#xff0c;资源样式发生改变&#xff0c;控件也会改变 2、资源字典

为什么越来越多的人转行网络安全?

为什么越来越多的人转行网络安全&#xff1f; 目前&#xff0c;我国互联网已经从前期的爆发增长进入稳定发展阶段&#xff0c;同时每年大量计算机相关专业的毕业生进入就业赛道&#xff0c;导致IT行业逐渐趋于饱和状态&#xff0c;甚至出现裁员现象&#xff0c;去年很多大厂都…

《DevOps 精要:业务视角》- 读书笔记(二)

DevOps 精要:业务视角&#xff08;二&#xff09; 第2章 基础2.1 精益生产2.1.1 关键事实2.1.2 挑战 2.2 敏捷2.2.1 关键事实2.2.2 挑战 第2章 基础 2.1 精益生产 2.1.1 关键事实 正如1.2节提到的&#xff0c;DevOps非常依赖于精益生产的原则与实践。有些人甚至相信&#xf…

软件测试(功能、工具、接口、性能、自动化、测开)详解

一、软件测试功能测试 测试用例编写是软件测试的基本技能&#xff1b;也有很多人认为测试用例是软件测试的核心&#xff1b;软件测试中最重要的是设计和生成有效的测试用例&#xff1b;测试用例是测试工作的指导&#xff0c;是软件测试的必须遵守的准则。 黑盒测试常见测试用例…

【1++的Linux】之文件(一)

&#x1f44d;作者主页&#xff1a;进击的1 &#x1f929; 专栏链接&#xff1a;【1的Linux】 文章目录 一&#xff0c;初识文件二&#xff0c;文件接口 一&#xff0c;初识文件 文件就是文件内容属性。因此对文件的操作无非就是对文件内容的操作和对文件属性的操作。 我们访问…