专栏首页业余草Unsafe 的 CAS 和内存操作的原理、源码解毒

Unsafe 的 CAS 和内存操作的原理、源码解毒

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xmt1139057136/article/details/88968991

点击上方“业余草”,选择“置顶公众号”

第一时间获取技术干货和业界资讯!

Java 语言的一大特点就是跨平台,并且提供的有一套完美的内存管理机制。但这都是 JVM 提供的,如果我们想要直接访问系统内存资源、自主管理内存资源等就无法实现。于是 Java 又提供了一个魔法类:Unsafe。

Unsafe 类位于 sun.misc 包中。从名字看,这个类就是一个不安全的类,实际上它确实是封装了一些不安全的操作!

Unsafe 类和 String 类一样的被定义为 final,也就是说它不可以被继承。并且 Unsafe 被设计成了单例,构造函数是私有的,只能通过 getUnsafe 方法获得它。除此之外,getUnsafe 方法还设置了限制条件,只有授信的代码才能获得该类的实例。哪些是授信的代码呢?当然是 JDK 库里面的类是可以随意使用的。

说了半天,这个类,我们无法使用,你讲它又何意义?

别急,Java 虽然不建议我们使用它,但是我们还是可以通过两种方式来使用它。

第一种方式是:让我们的代码在启动时“授信”。在运行程序时,指定 bootclasspath 选项,让你使用 Unsafe 实例的类被引导类加载器加载,从而通过 Unsafe.getUnsafe 方法安全的获取 Unsafe 实例。

这个做法比较少用,所以推荐大家采用第二种方法:通过反射来使用它。

注意有的 IDE 可能支持的不是很友好。比如:eclipse 显示”Access restriction…”错误,但如果你运行代码,它将正常运行。如果这个错误提示令人烦恼,可以通过以下设置来避免:

Unsafe 有 8 大功能,很多号主只讲了它的 CAS 功能。

如上图所示,Unsafe 提供的 105 个 API 大致可分为内存操作、CAS、Class 相关、对象操作、线程调度、系统信息获取、内存屏障、数组操作等。今天我先来说两个大功能:CAS 和内存操作(和我前面的《手把手教你通过Java代码体验强引用、软引用、弱引用、虚引用的区别》、《90%的程序员可能都不了解的堆外内存》都有些关联,这是一个系列)。

CAS 操作主要涉及到下面 3 个 API。

CAS 即比较并替换,实现并发算法时常用到的一种技术。CAS 操作包含三个操作数——内存位置、预期原值及新值。执行 CAS 操作的时候,将内存位置的值与预期原值比较,如果相匹配,那么处理器会自动将该位置值更新为新值,否则,处理器不做任何操作。我们都知道,CAS 是一条 CPU 的原子指令(cmpxchg 指令),不会造成所谓的数据不一致问题,Unsafe 提供的 CAS 方法(如 compareAndSwapXXX)底层实现即为 CPU 指令 cmpxchg。

CAS 在 java.util.concurrent.atomic 相关类、Java AQS、CurrentHashMap 等实现上有非常广泛的应用。比如,在 AtomicInteger 的实现中,静态字段 valueOffset 即为字段 value 的内存偏移地址,valueOffset 的值在 AtomicInteger 初始化时,在静态代码块中通过 Unsafe 的 objectFieldOffset 方法获取。在 AtomicInteger 中提供的线程安全方法中,通过字段 valueOffset 的值可以定位到 AtomicInteger 对象中 value 的内存地址,从而可以根据 CAS 实现对 value 字段的原子操作。

比如,下图就为某个 AtomicInteger 对象自增操作前后的内存示意图,对象的基地址 baseAddress=“0x110000”,通过 baseAddress+valueOffset 得到 value 的内存地址 valueAddress=“0x11000c”;然后通过 CAS 进行原子性的更新操作,成功则返回,否则继续重试,直到更新成功为止。

说完 CAS,我们再来说说 Unsafe 的内存操作。

内存操作主要有下面 9 个 API。

在《手把手教你通过Java代码体验强引用、软引用、弱引用、虚引用的区别》和《90%的程序员可能都不了解的堆外内存》两篇文章中,我已经讲过了。在 Java 中创建的对象都处于堆内内存(heap)中,堆内内存是由 JVM 所管控的 Java 进程内存,并且它们遵循 JVM 的内存管理机制,JVM 会采用垃圾回收机制统一管理堆内存。与之相对的是堆外内存,存在于 JVM 管控之外的内存区域,Java 中对堆外内存的操作,依赖于 Unsafe 提供的操作堆外内存的 native 方法。

使用堆外内存的原因是:

  • 对垃圾回收停顿的改善。由于堆外内存是直接受操作系统管理而不是 JVM,所以当我们使用堆外内存时,即可保持较小的堆内内存规模。从而在 GC 时减少回收停顿对于应用的影响。
  • 提升程序 I/O 操作的性能。通常在 I/O 通信过程中,会存在堆内内存到堆外内存的数据拷贝操作,对于需要频繁进行内存间数据拷贝且生命周期较短的暂存数据,都建议存储到堆外内存。

我前面提到的 DirectByteBuffer,在 Netty、MINA 等 NIO 框架中应用广泛。DirectByteBuffer 对于堆外内存的创建、使用、销毁等逻辑均由 Unsafe 提供的堆外内存 API 来实现。

上图为 DirectByteBuffer 构造函数,创建 DirectByteBuffer 的时候,通过 Unsafe.allocateMemory 分配内存、Unsafe.setMemory 进行内存初始化,而后构建 Cleaner 对象用于跟踪 DirectByteBuffer 对象的垃圾回收,以实现当 DirectByteBuffer 被垃圾回收时,分配的堆外内存一起被释放。具体的释放就是我前面讲的 PhantomReference 虚引用。

以上就是 Unsafe 类 8 大主要功能的 2 个重要的功能。其他功能,我会在对应使用到的框架脑图中串起来讲。当然,如果你们现在希望了解的话,我也可以提前写一下这方便的内容。选择权在于你们的留言和评论!

10T技术资源大放送!包括但不限于:C/C++,Linux,Python,Java,PHP,人工智能,GO等等。在公众号内回复对应关键字或框架名字,即可免费获取!!

 你再主动一点点 

  我们就有故事了

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 异常、堆内存溢出、OOM的几种情况

    【情况一】:    java.lang.OutOfMemoryError: Java heap space:这种是java堆内存不够,一个原因是真不够,另一个...

    业余草
  • 90%的程序员可能都不了解的堆外内存

    版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.n...

    业余草
  • JAVA_OPTS

    JVM:JAVA_OPTS="-server -Xms2048m -Xmx2048m -Xss512k"

    业余草
  • nginx内存池

    2、防止出错:统一在生命周期结束时通过销毁内存池释放所有资源,避免中间异常返回忘记释放资源,造成资源泄漏。

    用户1215536
  • 连续内存分配

    连续内存是一种比较直观的做法。这种做法将内存分为两个区域,一个是用户进程区域,另一个是操作系统区域。操作系统一般放在内存的低地址区域,这时因为中断向量被设置在低...

    zy010101
  • 经典面试题-GC是什么,为什么要有GC

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 ...

    cwl_java
  • Java中的堆和栈的区别

    当一个人开始学习Java或者其他编程语言的时候,会接触到堆和栈,由于一开始没有明确清晰的说明解释,很多人会产生很多疑问,什么是堆,什么是栈,堆和栈有什么区别?更...

    java达人
  • 浅谈计算机中的存储模型(一)物理内存

    今天,我们来了解一下计算机中的存储模型,大雄将这部分知识分成了三块,也就是我们会对这部分的知识推送三次。

    老九君
  • flink二三事(2):起家的技术

    上一篇聊到flink的历史,请看上篇 flink两三事 ----(1)历史。 可以说基本上是起了个大早,赶了个晚集,但是flink能做今天这种热度,没有被spa...

    大数据和云计算技术
  • 高性能:8-可用于Memory分析的BPF工具【bpf performance tools读书笔记】

    内核和处理器负责将虚拟内存映射到物理内存。为了提高效率,会在称为页面的内存组中创建内存映射,其中每个页面的大小是处理器的详细信息。尽管大多数处理器也支持更大的容...

    二狗不要跑

扫码关注云+社区

领取腾讯云代金券