零 前期准备

0 版本

JDK 版本 : OpenJDK 11.0.1

IDE : idea 2018.3

1 Unsafe 简介

Unsafe 是 java 留给开发者的后门,用于直接操作系统内存且不受 jvm 管辖,实现类似 C++ 风格的操作。

Oracle 官方一般不建议开发者使用 Unsafe 类,因为正如这个类的类名一样,它并不安全,使用不当会造成内存泄露。

在平时的业务开发中,这个类基本是不会有接触到的,但是在 java 的并发包和众多偏向底层的框架中,都有大量应用。

值得一提的是,该类的大部分方法均为 native 修饰,即为直接调用的其它语言(大多为 C++)编写的方法来进行操作,很多细节无法追溯,只能大致了解。

一 Unsafe 的获取

jdk8 中的 Unsafe 在包路径 sun.misc 下,引用全名 sun.misc.Unsafe。而在 jdk9 中,官方在 jdk.internal.misc 包下又增加了一个 Unsafe 类,引用全名 jdk.internal.misc.Unsafe。

这两个 Unsafe 的构造方法均被 private 修饰,且类中有一个自身的静态实例对象,即经典的单例模式实现,并且提供了 getUnsafe() 方法调用:

Unsafe unsafe = Unsafe.getUnsafe();

但是其实这个方法是无法在日常开发中使用的,具体的等下分析。

1 jdk.internal.misc.Unsafe

从代码量和注释量上来说,jdk.internal.misc.Unsafe 比另一者要丰富一些。

但是笔者尝试之后发现该类应该是无法直接在代码中使用的。

该类位于 java.base 模块下,根据一些网络资料,笔者尝试在 idea 的 compiler.xml 文件中导入了该模块:

--add-exports=java.base/jdk.internal.misc=ALL-UNNAMED

以及在 maven 的 pom.xml 中加入该模块:

--add-exports=java.base/jdk.internal.misc=ALL-UNNAMED

任然无法使用该类,启动报错:

Exception in thread "main" java.lang.IllegalAccessError: class test.jdk.UnsafeTest (in unnamed module @0x57829d67) cannot access class jdk.internal.misc.Unsafe (in module java.base) because module java.base does not export jdk.internal.misc to unnamed module @0x57829d67

有一些大神在博文中提到在 jdk9 中可以使用导入模块去使用该类,笔者未做尝试。

可能有一些别的蹊径可以使用该类,但是笔者对于 jdk 中的模块系统也不算特别熟悉,本题是研究 Unsafe 的使用,所以这部分暂时不多研究了。

2 sun.misc.Unsafe

sun.misc.Unsafe 是 jdk 中一直存在的 Unsafe,一般的第三方库的实现会使用该类。

该类在 jdk9 之后移动到了 jdk.unsupported 模块中。

在 jdk.unsupported 模块的 module-info.class 中可以看到:

// jdk.unsupported 模块下的 module-info.class

module jdk.unsupported {

exports com.sun.nio.file;

exports sun.misc; // sun.misc.Unsafe 所在的路径

exports sun.reflect;

opens sun.misc;

opens sun.reflect;

}

也就是说该模块将该类开放了出来,其它应用可以使用该类。

在 jdk11 中,该类的 api 实现很有意思:

// sun.misc.Unsafe.class

@ForceInline

public void putInt(Object o, long offset, int x) {

theInternalUnsafe.putInt(o, offset, x);

}

@ForceInline

public Object getObject(Object o, long offset) {

return theInternalUnsafe.getObject(o, offset);

}

@ForceInline

public void putObject(Object o, long offset, Object x) {

theInternalUnsafe.putObject(o, offset, x);

}

@ForceInline

public boolean getBoolean(Object o, long offset) {

return theInternalUnsafe.getBoolean(o, offset);

}

@ForceInline

public void putBoolean(Object o, long offset, boolean x) {

theInternalUnsafe.putBoolean(o, offset, x);

}

...

此处仅举部分例子,在这个 Unsafe 类中,大多数的实现都调用了 theInternalUnsafe 这个对象的相关方法。

而这个对象,则是一个 jdk.internal.misc.Unsafe 对象:

// sun.misc.Unsafe.class

private static final jdk.internal.misc.Unsafe theInternalUnsafe = jdk.internal.misc.Unsafe.getUnsafe();

在 java.base 的 module-info.class 中笔者也看到了这样的配置:

// java.base 模块下的 module-info.class

exports jdk.internal.misc to // jdk.internal.misc 是 jdk.internal.misc.Unsafe 所在的包路径

java.desktop,

java.logging,

java.management,

java.naming,

java.net.http,

java.rmi,

java.security.jgss,

java.sql,

java.xml,

jdk.attach,

jdk.charsets,

jdk.compiler,

jdk.internal.vm.ci,

jdk.jfr,

jdk.jlink,

jdk.jshell,

jdk.net,

jdk.scripting.nashorn,

jdk.scripting.nashorn.shell,

jdk.unsupported; // jdk.unsupported 是 sun.misc.Unsafe 所在的模块

可见,java.base 只是将该类所在的包路径开放给了有限的几个模块,而没有完全开放给广大开发者。

看到此处,大致可以猜想,Oracle 应该是希望使用 jdk.internal.misc.Unsafe 作为真正的 Unsafe 使用,但是为了兼容性考虑保留了 sun.misc.Unsafe。

并且其实从 api 来说,jdk.internal.misc.Unsafe 的数量更多,权限更大;sun.misc.Unsafe 则比较有限。

在这里说一些题外话,从 jdk.unsupported 这个模块名可以看出,Oracle 确实不太希望开发者使用该模块内的类,甚至 Oracle 在未来的版本里是有可能完全封闭 Unsafe 的使用的,早在 jdk9 时期就有类似传闻。

但是笔者站在一个普通开发者的角度,其实不太希望这样的情况出现,因为笔者认为 Oracle 作为 java 的标准制定者,应该给 java 留下足够的自由度,让开发者能够充分发挥聪明才智开发出更强大的轮子,成熟的开发者应该能为自己的行为负责,而不需要官方摆出一幅 我来手把手教你 的模样。

3 Unsafe 对象获取

由于 jdk.internal.misc.Unsafe 无法使用,所以以下均使用 sun.misc.Unsafe 来做 demo。

之前提到了 Unsafe 类的 getUnsafe() 静态获取单例的方法,但是其实那个方法是不对普通开发者开放的,笔者尝试使用之后报错:

Exception in thread "main" java.lang.SecurityException: Unsafe

笔者查看了一些第三方库对 Unsafe 的使用,也确实不会直接使用该方式,而是使用反射机制去获取该类:

try {

// 获取 Unsafe 内部的私有的实例化单例对象

Field field = Unsafe.class.getDeclaredField("theUnsafe");

// 无视权限

field.setAccessible(true);

unsafe = (Unsafe) field.get(null);

} catch (NoSuchFieldException e) {

e.printStackTrace();

} catch (IllegalAccessException e) {

e.printStackTrace();

}

二 内存

在 Unsafe 中可以直接申请一块内存:

// 需要传入一个 long 类型的参数,作为申请的内存的大小,单位为 byte

// 返回这块内存的 long 类型地址

long memoryAddress = unsafe.allocateMemory(8);

Unsafe 申请的内存不在 jvm 管辖范围内,需要手动释放:

//传入之前申请的内存的地址就可以释放该块内存了

unsafe.freeMemory(memoryAddress);

注意,如果申请了内存,但是中途报错导致中断了代码执行,没有执行内存的释放,就出现了内存泄漏。所以为了保险起见,实际生产中尽量在 finally 区域里进行内存的释放操作。

还有一个重新分配内存的方法:

// 传入之前申请的内存的地址和一个 long 类型的参数作为新的内存的 byte 大小

// 此方法会释放掉之前地址的内存,然后重新申请一块符合要求大小的内存

// 如果之前那块内存上已经存在对象了,就会被拷贝到新的内存上

long newMemoryAddress = unsafe.reallocateMemory(memoryAddress, 32);

三 存取对象

Unsafe 中有数量众多的 put 和 get 方法,用于将对象存入内存或者从内存中获取值。原理类似,可以选取几个来进行理解:

// 将 int 型整数 5 存入到指定地址中

unsafe.putInt(menmoryAddress,5);

// 根据地址获取到整数

int a = unsafe.getInt(menmoryAddress);

// 打印,得到 5

System.out.println(a);

这是最基本的 putInt 和 getInt 的运用,除此之外还有 putLong/getLong、putByte/getByte 等等,覆盖了几个基本类型。

但是 put 和 get 方法还有一套常用的重载方法,在这里先借助一个 bean 进行测试:

class UnsafeBean{

// 测试1 测试 static 修饰的 int 类型的存取

private static int staticInt = 5;

// 测试2 测试 static 修饰的 object 类型的存取

private static String staticString = "static_string";

// 测试3 测试 final 修饰的 int 类型的存取

private final int finalInt = 5;

// 测试4 测试 final 修饰的 object 类型的存取

private final String finalString = "final_string";

// 测试5 测试一般的 int 类型的存取

private int privateInt;

// 测试6 测试一般的 object 类型的存取

private String privateString;

}

测试内容:

UnsafeBean bean = new UnsafeBean();

// 1 测试 staticInt

// 先通过变量名反射获取到该变量

Field staticIntField = UnsafeBean.class.getDeclaredField("staticInt");

//无视权限

staticIntField.setAccessible(true);

//staticFieldOffset(...) 方法能够获取到类中的 static 修饰的变量

long staticIntAddress = unsafe.staticFieldOffset(staticIntField);

// 使用 put 方法进行值改变,需要传入其所在的 class 对象、内存地址和新的值

unsafe.putInt(UnsafeBean.class,staticIntAddress,10);

// 使用 get 方法去获取值,需要传入其所在的 class 对象和内存地址

int stiatcIntTest = unsafe.getInt(UnsafeBean.class,staticIntAddress);

// 此处输出为 10

System.out.println(stiatcIntTest);

// 2 测试 staticString

// 基本流程相同,只是 put 和 get 方法换成了 getObject(...) 和 putObject(...)

Field staticStringField = UnsafeBean.class.getDeclaredField("staticString");

staticStringField.setAccessible(true);

long staticStringAddress = unsafe.staticFieldOffset(staticStringField);

unsafe.putObject(UnsafeBean.class,staticStringAddress,"static_string_2");

String staticStringTest = (String)unsafe.getObject(UnsafeBean.class,staticStringAddress);

/// 此处输出为 static_string_2

System.out.println(staticStringTest);

// 3 测试 finalInt

// 基本流程相同,只是 staticFieldOffset(...) 方法换成了 objectFieldOffset(...) 方法

Field finalIntField = UnsafeBean.class.getDeclaredField("finalInt");

finalIntField.setAccessible(true);

long finalIntAddress = unsafe.objectFieldOffset(finalIntField);

// 需要注意的是,虽然该变量是 final 修饰的,理论上是不可变的变量,但是 unsafe 是具有修改权限的

unsafe.putInt(bean,finalIntAddress,10);

int finalIntTest = unsafe.getInt(bean,finalIntAddress);

// 此处输出为 10

System.out.println(finalIntTest);

// 4 测试 finalString

Field finalStringField = UnsafeBean.class.getDeclaredField("finalString");

finalStringField.setAccessible(true);

long finalStringAddress = unsafe.objectFieldOffset(finalStringField);

unsafe.putInt(bean,finalStringAddress,"final_string_2");

String finalStringTest = (String)unsafe.getObject(bean,finalStringAddress);

/// 此处输出为 final_string_2

System.out.println(finalStringTest);

// 测试5 和 测试6 此处省略,因为和上述 final 部分的测试代码一模一样

put 和 get 方法还有一组很类似的 api,是带 volatile 的:

public int getIntVolatile(Object o, long offset);

public void putIntVolatile(Object o, long offset, int x);

public Object getObjectVolatile(Object o, long offset);

public void putObjectVolatile(Object o, long offset, Object x);

...

这一组 api 的使用方式和上述一样,只是增加了对 volatile 关键词的支持。测试发现,该组 api 也支持不使用 volatile 关键词的变量。

get 和 put 方法的思路都比较简单,使用思路可以归纳为:

1 用反射获取变量对象 (getDeclaredField)

2 开放权限,屏蔽 private 关键字的影响 (setAccessible(true))

3 调用相关方法获取到该对象中的该变量对象的内存地址 (staticFieldOffset/objectFieldOffset)

4 通过内存地址去修改该对象的值 (putInt/putObject)

5 获取对象的值 (getInt/getObject)

四 线程的挂起和恢复

线程的挂起调用 park(...) 方法:

// 该方法第二个参数为 long 类型对象,表示该线程准备挂起到的时间点

// 注意,此为时间点,而非时间,该时间点从 1970 年(即元年)开始

// 第一个参数为 boolean 类型的对象,用来表示挂起时间的单位,true 表示毫秒,false 表示纳秒

// 第一个参数为 true,第二个参数为 0 的时候,线程会直接返回,不太清楚机理

unsafe.park(false,0L);

与之对应的 unpark(...) 方法:

// 此处传入线程对象

unsafe.unpark(thread);

请注意,挂起时是不需要传入线程对象的,即只有线程自身可以执行此方法用于挂起自身,但是恢复方法是需要其它线程来帮助恢复的。

五 CAS

Unsafe 中提供了一套原子化的判断和值替换 api,来看一下例子:

// 创建一个 Integer 对象,value 为 1

Integer i = 1;

// 获取到内部变量 value,这个变量用于存放值

Field valueField = Integer.class.getDeclaredField("value");

valueField.setAccessible(true);

// 获取到内存偏移量值

long valueAddress = unsafe.objectFieldOffset(valueField);

// getIntVolatile 使用偏移量获取到对象内指定变量的值

int originValue_1 = unsafe.getIntVolatile(i,valueAddress);

// getIntVolatile 获取 int 的值

// getDoubleVolatile 获取 double 的值

// getLongVolatile 获取 long 的值

// getObjectVolatile 获取 object 的值

// getAndAddInt 方法和 getIntVolatile 一样会获取到变量的值,同时会加上一个值

int originValue_2 = unsafe.getAndAddInt(i,valueAddress,5);

// getAndAddInt 获取值并加上一个 int 类型的整数

// getAndAddLong 获取值并加上一个 long 类型的整数

// getAndAddInt 方法和 getIntVolatile 一样会获取到变量的值,同时会替换一个值

int originValue_3 = unsafe.getAndSetInt(i,valueAddress,5);

// 该方法用户比较及替换值

// 第一个参数为要替换的对象本身,第二个参数为值的内存地址

// 第三个参数为变量的预期值,第四个参数为变量要换的值

// 如果变量目前的值等于预期值(第三个参数),则会将变量的值换成新值(第四个参数),返回 true

// 如果不等于预期,则不会改变,并返回 false

boolean isOk = unsafe.compareAndSwapInt(i,valueAddress,1,5);

//此处输出 true

System.out.println(isOk);

//此处输出 5

System.out.println(i);

六 一点唠叨

Unsafe 的 api 众多,但是网络资料不多,且功能较为晦涩,不太好写 demo。但是在近期学习 jdk 并发包的时候经常会接触到,所以在此先记录一些看到过的方法的具体应用。其它的有缘补充。

unsafe java_浅析 Unsafe 的使用相关推荐

  1. golang unsafe.Sizeof浅析

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/HaoDaWang/article/de ...

  2. C语言变量unsafe,Java中Unsafe使用详解

    Unsafe介绍 Unsafe是位于sun.misc包下的一个类,主要提供一些用于执行低级别.不安全操作的方法,如直接访问系统内存资源.自主管理内存资源等,这些方法在提升Java运行效率.增强Java ...

  3. Unsafe code may only appear if compiling with /unsafe. Enable “Allow ‘unsafe‘ code“

    Unsafe code may only appear if compiling with /unsafe. Enable "Allow 'unsafe' code" in Pla ...

  4. java unsafe park_Java中Unsafe类详解

    http://www.cnblogs.com/mickole/articles/3757278.html Java不能直接访问操作系统底层,而是通过本地方法来访问.Unsafe类提供了硬件级别的原子操 ...

  5. java unsafe 类_Java Unsafe类的使用

    Java Unsafe类的使用 Unsafe类的作用 Unsafe类是rt.jar包中的类,它提供了原子级别的操作,它的方法都是native方法,通过JNI访问本地的C++库.它的出现是为了解决在高并 ...

  6. tlab java_浅析java中的TLAB

    好久,好久....没有更博客了.这一次利用闲暇时间,来扯一下关于JVM中的TLAB. 什么是TLAB?它是干什么的?咋们先抛开这个问题,一切的开始得从new对象到指针碰撞开始讲起. new对象与指针碰 ...

  7. murmurhash java_浅析ketamahash和murmurhash

    说来赶巧,之前我有16个redis集群,然后我要将某个key根据路由规则存到16个集群中的某一个上面,正巧用到了这两种哈希算法,改造完毕上线后,整体带来的效果也十分理想. 说道ketamahash,它 ...

  8. arcengine java_浅析 ArcEngine Java - EngineViewer 例子

    例子源文件:\DeveloperKit\samples\Applications\EngineViewer\Java\EngineViewer.jar 运行环境的搭建: 解压后,在Eclipse或Jb ...

  9. 车票购买最低消费问题java_浅析12306售票算法(java版)

    1.以G71列车为例,首先对车次站台进行占位编码(从1开始到最后一站递加) 对以上占位简单描述以下:G71总共18个站点那么我们的单个座位的座位标识可以用十八位长度的二进制字符串表示100000000 ...

最新文章

  1. 【攻防世界015】notsequence
  2. 在python中使用关键字define定义函数_python等价于'define func()'或如何在python中注释掉函数调用...
  3. opencv-api getPerspectiveTransform
  4. datagrid 溢出文本显示省略号
  5. 获取当前3Ds MAX版本
  6. mybatis一对多和多对一
  7. oracle怎么查找数据泵,ORACLE数据泵使用详解
  8. mse 反编译_专业Delphi反编译工具(DeDeDark)
  9. 管家婆 打开经营历程 Date exceeds maximum of 19-12-31报错解决
  10. 2013,爱上暗色调
  11. es创建索引和yellow排查解决
  12. AHRS 原理算法+代码实现(好文记录)
  13. 秋招总结|阿里转正失败,到拿到10个大厂产品offer
  14. 计算机硬件设备介绍 ppt,计算机硬件设备的认识课件.ppt
  15. ResponseResult
  16. ParaView-1
  17. 新的起点,梦的启航!我为什么要写这个博客
  18. SCU2511(单调栈)
  19. scratch项目:自制电子画板(Scratch画笔类积木、事件类积木中消息广播的应用)
  20. 基于逻辑回归的新闻数据集分类

热门文章

  1. FastDFS合并存储原理分析
  2. 欣然随风的表单验证类
  3. 欺骗的艺术——第二部分(10)
  4. [转载:越南很有魄力啊]越南政府所有电脑将使用Linux
  5. typescript学习笔记1 —— 接口(interface)
  6. 60亿部手机被闲置?解锁让新机“降价”秘诀
  7. Android快速设置Quick Settings Tile
  8. securecrt7.0破解版安装与注册机的使用方法
  9. leetcode 662.二叉树最大深度 Java
  10. JAVA实现图片特效