各大框架都在使用的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() {}@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提供的功能:

98efed0cda6c70adb531b0c4a5c51a5d.png
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类的主要目的大多数情况下是为了提升运行效率、增强功能。但同时也面临着出错、内存管理等风险。只有深入了解,且有必要的情况下才建议使用。

a9ab3e1e3851ad3ea1c562e9bf614c0d.gif

往期推荐

9d68c4ea64e912521fd269b0af8ee57b.png

synchronized底层是如何实现的?


5a6a2289ff8ac39a6f7e1c7d5a1a33b3.png

线程休眠竟然有 5 种方法?


3eb1a75c62aaa1152a84db48867d4f76.png

虾皮二面:什么是零拷贝?如何实现零拷贝?


fdae7600e60f4cd10b62decaff0bc3e8.gif

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

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

相关文章

Codis 分布式缓存部署

为什么80%的码农都做不了架构师&#xff1f;>>> 环境介绍: 1:机器三台 ,IP/hostname 如下, hostname的设置很重要zookeeper / codis的通信都会用到,所以要配置好三台机器的hosts文件. 10.221.8.220 机器的hostname为 Redis1 10.221.8.221 机器的hostname为 Redis…

怎么解决MySQL死锁问题的?

咱们使用 MySQL 大概率上都会遇到死锁问题&#xff0c;这实在是个令人非常头痛的问题。本文将会对死锁进行相应介绍&#xff0c;对常见的死锁案例进行相关分析与探讨&#xff0c;以及如何去尽可能避免死锁给出一些建议。话不多说&#xff0c;开整&#xff01;什么是死锁死锁是并…

Apache cxf JaxRs基本应用

2019独角兽企业重金招聘Python工程师标准>>> 在前一篇中&#xff0c;我们完成了《Apache cxf JaxWs基本应用》 的编写&#xff0c;我们现在实现一个Restful风格的Cxf 。 一、我们首先依旧是基于Maven project配置pom.xml的依赖 [html] view plaincopyprint? <pr…

白嫖1年阿里云,反手就搭一个Java环境

作者 | 磊哥来源 | Java中文社群&#xff08;ID&#xff1a;javacn666&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone&#xff09;早上收到阿里云小姐姐的消息&#xff0c;阿里云有搞事情了&#xff0c;这次是送一年的阿里云 ECS 服务器。有便宜不占王八蛋…

synchronized和ReentrantLock的5个区别!

作者 | 磊哥来源 | Java面试真题解析&#xff08;ID&#xff1a;aimianshi666&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone&#xff09;在 Java 中&#xff0c;常用的锁有两种&#xff1a;synchronized&#xff08;内置锁&#xff09;和 ReentrantLock&a…

《小强升职记》读后感和思维导图

语言幽默轻松&#xff0c;寓教于乐&#xff0c;看完之后有挽起袖子大干一场的冲动&#xff0c;但是诚如书中所言&#xff0c;“不做收藏家&#xff0c;要做建筑工”&#xff0c;实践和坚持才能有所收获。第一次画思维导图(′▽〃)Xmind格式文件转载于:https://www.cnblogs.com/…

oppo后端16连问

前言 大家好&#xff0c;我是磊哥。最近有位读者去面试了oppo&#xff0c;给大家整理了面试真题的答案。希望对大家有帮助哈&#xff0c;一起学习&#xff0c;一起进步。聊聊你印象最深刻的项目&#xff0c;或者做了什么优化。你项目提到分布式锁&#xff0c;你们是怎么使用分布…

实战!阿里神器 Seata 实现 TCC 模式解决分布式事务

今天这篇文章介绍一下Seata如何实现TCC事务模式&#xff0c;文章目录如下&#xff1a;目录什么是TCC模式&#xff1f;TCC&#xff08;Try Confirm Cancel&#xff09;方案是一种应用层面侵入业务的两阶段提交。是目前最火的一种柔性事务方案&#xff0c;其核心思想是&#xff1…

Spring Boot 中实现跨域的 5 种方式,你一定要知道!

一、为什么会出现跨域问题出于浏览器的同源策略限制。同源策略&#xff08;Sameoriginpolicy&#xff09;是一种约定&#xff0c;它是浏览器最核心也最基本的安全功能&#xff0c;如果缺少了同源策略&#xff0c;则浏览器的正常功能可能都会受到影响。可以说Web是构建在同源策略…

术中导航_密码术中的计数器(CTR)模式

术中导航The Counter Mode or CTR is a simple counter based block cipher implementation in cryptography. Each or every time a counter initiated value is encrypted and given as input to XOR with plaintext or original text which results in ciphertext block. Th…

Android社交类APP动态详情代码实现通用模板

&#xfeff;&#xfeff;Android社交类APP动态详情代码实现通用模板 Android平台上一些比较流行的社交类APP比如微信、陌陌等&#xff0c;都有动态详情页&#xff0c;在该页面&#xff0c;用户发表的动态详情&#xff0c;好友可以发起评论、点赞等等。这种设计在微信和陌陌上大…

聊聊并发编程的12种业务场景

前言并发编程是一项非常重要的技术&#xff0c;无论在面试&#xff0c;还是工作中出现的频率非常高。并发编程说白了就是多线程编程&#xff0c;但多线程一定比单线程效率更高&#xff1f;答&#xff1a;不一定&#xff0c;要看具体业务场景。毕竟如果使用了多线程&#xff0c;…

梳理50道经典计算机网络面试题

我梳理了50道计算机网络面试题&#xff0c;每一道题目都特别经典&#xff0c;大厂也非常喜欢问。相信大家看完&#xff0c;会有新的收获滴~1. 说说HTTP常用的状态码及其含义&#xff1f;思路: 这道面试题主要考察候选人&#xff0c;是否掌握HTTP状态码这个基础知识点。不管是不…

A successful Git branching model

原文&#xff1a;http://nvie.com/posts/a-successful-git-branching-model/ In this post I present the development model that I’ve introduced for all of my projects (both at work and private) about a year ago, and which has turned out to be very successful. I…

一文详解读写锁

作者 | 磊哥来源 | Java面试真题解析&#xff08;ID&#xff1a;aimianshi666&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone&#xff09;读写锁&#xff08;Readers-Writer Lock&#xff09;顾名思义是一把锁分为两部分&#xff1a;读锁和写锁&#xff0c…

[实战]MVC5+EF6+MySql企业网盘实战(2)——用户注册

写在前面 上篇文章简单介绍了项目的结构&#xff0c;这篇文章将实现用户的注册。当然关于漂亮的ui&#xff0c;这在追后再去添加了&#xff0c;先将功能实现。也许代码中有不合适的地方&#xff0c;也只有在之后慢慢去优化了。 系列文章 [EF]vs15ef6mysql code first方式 [实战…

下个十年高性能 JSON 库来了:fastjson2!

作者 | 磊哥来源 | Java中文社群&#xff08;ID&#xff1a;javacn666&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;GG_Stone&#xff09;fastjson2 是 fastjson 项目的重要升级&#xff0c;目标是为下一个十年提供一个高性能的 JSON 库&#xff0c;同一套 API 支…

THEOS的第一个TWeak的成功创建

THEOS的第一个TWeak的成功创建THEOS的第一个TWeak的成功创建参考资料:成功的创建一个TWeak的弹出步骤1:安装Xcode和Xcode command line步骤2:安装theosa:下载theos前,设置保存的路径:环境变量b:下载theosc:下载头文件d:下载ldid签名工具e:配置MoblieSubstrate环境f:安装dpkg步骤…

查询中,有没有可能多个索引一起用呢?

其实我们之前所讲的回表&#xff0c;就是两个索引树同时使用&#xff0c;先在二级索引树中搜索到对应的主键值&#xff0c;然后在再去主键索引树中查询完整的记录。但是我今天的问题是&#xff0c;两个不同的二级索引树&#xff0c;会同时生效吗&#xff1f;理论上来说&#xf…

ThreadLocal夺命11连问

前言前一段时间&#xff0c;有同事使用ThreadLocal踩坑了&#xff0c;正好引起了我的兴趣。所以近期&#xff0c;我抽空把ThreadLocal的源码再研究了一下&#xff0c;越看越有意思&#xff0c;发现里面的东西还真不少。我把精华浓缩了一下&#xff0c;汇集成了下面11个问题&…