【分享】各大框架都在使用的Unsafe类

前言

几乎每个使用 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() {
    }

    @CallerSensitive
    public 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()[];
  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中有广泛的应用,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类的主要目的大多数情况下是为了提升运行效率、增强功能。但同时也面临着出错、内存管理等风险。只有深入了解,且有必要的情况下才建议使用。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

大型语言模型有什么用?

大型语言模型有什么用&#xff1f; 大型语言模型识别、总结、翻译、预测、生成文本和其他内容。 AI 应用程序正在总结文章、撰写故事和进行长时间对话——而大型语言模型正在承担繁重的工作。 大型语言模型或 LLM 是一种深度学习算法&#xff0c;可以根据从海量数据集中获得…

linux进阶篇:下载工具wget的安装以及应用

1 wget工具介绍 wget是一个下载文件的工具&#xff0c;它用在命令行下。对于Linux用户是必不可少的工具&#xff0c;我们经常要下载一些软件或从远程服务器恢复备份到本地服务器。 wget支持HTTP&#xff0c;HTTPS和FTP协议&#xff0c;可以使用HTTP代理。所谓的自动下载是指&a…

【C语言基础】:文件操作详解(后篇)

文章目录 一、文件的顺序读写1.1 顺序函数读写函数介绍1.2 fgetc函数和fputc函数1.3 fputs函数和fgets函数1.4 fprintf函数和fscanf函数1.5 fwrite函数和fread函数 二、文件的随机读写2.1 fseek函数2.2 ftell函数2.3 rewind函数 三、文件读取结束的判定3.1 feof函数 四、文件缓…

Linux mmap

目录 内存映射概念&#xff1a; 函数原型&#xff1a; 内存映射步骤&#xff1a; 主要功能 &#xff1a; 系统调用mmap用于共享内存的两种方式&#xff1a; 使用普通文件提供的内存映射&#xff1a; 使用特殊文件提供匿名内存映射&#xff1a; 注意事项 &#xff1a; …

【漏洞复现】潍微科技-水务信息管理平台 ChangePwd SQL注入漏洞

0x01 产品简介 潍微科技-水务信息管理平台主要帮助水务企业实现水质状态监测、管网运行监控、水厂安全保障、用水实时监控以及排放有效监管,确保居民安全稳定用水、环境有效保护,全面提升水务管理效率。 0x02 漏洞概述 潍微科技-水务信息管理平台 ChangePwd 接口存在SQL注…

YOLOv8改进 | 检测头篇 | 自研超分辨率检测头HATHead助力超分辨率检测(混合注意力变换器检测头)

一、本文介绍 本文给大家带来的改进机制是由由我本人利用HAT注意力机制&#xff08;超分辨率注意力机制&#xff09;结合V8检测头去掉其中的部分内容形成一种全新的超分辨率检测头。混合注意力变换器&#xff08;HAT&#xff09;的设计理念是通过融合通道注意力和自注意力机制…

Windows Edge 兼容性问题修复:提升用户体验的关键步骤

&#x1f31f; 前言 欢迎来到我的技术小宇宙&#xff01;&#x1f30c; 这里不仅是我记录技术点滴的后花园&#xff0c;也是我分享学习心得和项目经验的乐园。&#x1f4da; 无论你是技术小白还是资深大牛&#xff0c;这里总有一些内容能触动你的好奇心。&#x1f50d; &#x…

【随笔】Git 基础篇 -- 远程仓库 git clone(二十五)

&#x1f48c; 所属专栏&#xff1a;【Git】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &#x1f496; 欢迎大…

FTP所有操作

产生告警原理&#xff1a; 告警中出现 FTP STOP 关键字。Hawkeye keylogger木马名

iPad 无法解锁?修复 iPad 滑动解锁不起作用的 9 个解决方案

“我的 iPad Pro 一整天都工作正常&#xff0c;直到 20 分钟前。当我解锁它时&#xff0c;它不让我向上滑动。屏幕有响应&#xff0c;但我的 iPad 无法解锁。是否有其他人遇到过这种情况并找到了解决方法&#xff1f;解决方案&#xff1f;” ——来自 Apple 支持社区 iPad 屏幕…

前端开发攻略---根据音频节奏实时绘制不断变化的波形图。深入剖析如何通过代码实现音频数据的可视化。

1、演示 2、代码分析 逐行解析 JavaScript 代码块&#xff1a; const audioEle document.querySelector(audio) const cvs document.querySelector(canvas) const ctx cvs.getContext(2d)这几行代码首先获取了 <audio> 和 <canvas> 元素的引用&#xff0c;并使用…

Quartz + SpringBoot 实现分布式定时任务

文章目录 前言一、分布式定时任务解决方案二、Quartz是什么&#xff1f;1.quartz简介2.quartz的优缺点 二、Quartz分布式部署总结 前言 因为应用升级&#xff0c;由之前的单节点微服务应用升级为集群微服务应用&#xff0c;所以之前的定时任务Spring Scheduled不再适用了&…

进程等待waitwaitpid

文章目录 进程等待进程等待的必要性进程等待的方法waitwaitpidstatus 非阻塞等待 进程等待 任何子进程&#xff0c;在退出的情况下&#xff0c;一般必须要被父进程等待 进程等待的必要性 1.父进程通过等待&#xff0c;解决子进程退出的僵尸问题&#xff0c;回收系统资源。 2.…

基于springboot实现知识管理系统项目【项目源码+论文说明】计算机毕业设计

基于springboot实现知识管理系统演示 摘要 随着信息互联网信息的飞速发展&#xff0c;无纸化作业变成了一种趋势&#xff0c;针对这个问题开发一个专门适应师生作业交流形式的网站。本文介绍了知识管理系统的开发全过程。通过分析企业对于知识管理系统的需求&#xff0c;创建了…

012:vue结合纯CSS实现蛇形流程图/步骤条

文章目录 1. 实现效果2. 实现代码 1. 实现效果 2. 实现代码 <template><div class"container"><div v-for"(item, index) in list" class"grid-item"><div class"step">step{{index1}}</div></div&…

大厂Java笔试题之对完全数的处理

题目&#xff1a;完全数&#xff08;Perfect number&#xff09;&#xff0c;又称完美数或完备数&#xff0c;是一些特殊的自然数。 它所有的真因子&#xff08;即除了自身以外的约数&#xff09;的和&#xff08;即因子函数&#xff09;&#xff0c;恰好等于它本身。 例如&…

赋能力量,幸福花开 ——罗湖区懿米阳光开启全职妈妈社工培育计划

最美人间四月天&#xff0c;不负春光不负卿。 四月&#xff0c;迎来了全国社会工作师考试报名的日子&#xff0c;罗湖区全职妈妈妇联与罗湖区阳光妈妈妇联在服务过程中发现&#xff0c;全职妈妈们有获得社会工作师职业资格证的需求&#xff0c;为了更好地针对这一需求&#xf…

YOLOv5原创优化 : loss优化 | 一种新的自适应阈值焦点损失函数loss,增强目标特征,助力红外小目标暴力涨点

💡💡💡问题点:注意到红外小目标图像中目标与背景之间存在极大的不平衡,这使得模型更加关注背景特征而不是目标特征 💡💡💡解决对策:提出了一种新的自适应阈值焦点损失函数,该函数将目标和背景解耦,并利用自适应机制来调整损失权重,迫使模型将更多的注意力分配…

vs调试教程

官网链接&#xff1a; Microsoft Learn&#xff1a;培养开拓职业生涯新机遇的技能通过文档和培训习得技术技能、获得认证并与社区建立联系https://learn.microsoft.com/zh-cn/调试教程 调试器文档 - Visual Studio (Windows) | Microsoft Learn浏览文档&#xff0c;以帮助你使…

大厂Java笔试题之与7有关的数

题目&#xff1a;输出 1到n之间 的与 7 有关数字的个数。 一个数与7有关是指这个数是 7 的倍数&#xff0c;或者是包含 7 的数字&#xff08;如 17 &#xff0c;27 &#xff0c;37 ... 70 &#xff0c;71 &#xff0c;72 &#xff0c;73...&#xff09; 比如输入20&#xff0c;…
最新文章