Unsafe 是 sun.misc 包下的一个类,可以直接操作堆外内存,可以随意查看及修改 JVM 中运行时的数据,使 Java 语言拥有了类似 C 语言指针一样操作内存空间的能力。

Unsafe 的操作粒度不是类,而是内存地址和所对应的数据,增强了 Java 语言操作底层资源的能力。

一、获得 Unsafe 实例

查看 Unsafe.java 源码:https://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/3ef3348195ff/src/share/classes/sun/misc/Unsafe.java

public final class Unsafe {
private Unsafe() {} // 单例对象
private static final Unsafe theUnsafe = new Unsafe(); @CallerSensitive
public static Unsafe getUnsafe() {
Class<?> caller = Reflection.getCallerClass();
// 仅在引导类加载器 BootstrapClassLoader 加载时才合法
if (!VM.isSystemDomainLoader(caller.getClassLoader()))
throw new SecurityException("Unsafe");
return theUnsafe;
}
}

由此可以看出自己写的类即不能 new Unsafe() 对象也不能调用其 getUnsafe() 方法。

若想使用这个类只能通过其它方法,有如下两个可行方案。

1.让自己写的类使用 BootstrapClassLoader 加载

# unix 使用:号,windows 使用;号,这里以 windows 为例,使用了 Unsafe 类的 jar 包路径为 /hone/myUnsafe.jar
java -Xbootclasspath/a;/home/myUnsafe.jar com.unsafeTest

2.通过反射获取单例对象 theUnsafe

private static Unsafe reflectGetUnsafe() {
try {
// 获得 theUnsafe 属性对象
Field field = Unsafe.class.getDeclaredField("theUnsafe");
// 取消权限控制检查,让其可获得 private 修饰属性
field.setAccessible(true);
// 获得 Unsafe.class 的 theUnsafe 属性(如果底层字段是一个静态字段,则忽略 obj 参数;它可能为 null)
return (Unsafe) field.get(null);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

二、Unsafe 常用 API 介绍

Unsafe 类大部分都是 native 方法,具体实现由 JVM 完成:https://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/7576bbd5a03c/src/share/vm/prims/unsafe.cpp

1.内存操作(堆外内存)

// 分配内存, 相当于 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);

2.CAS

/**
* 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);

3.线程调度

// 阻塞线程
public native void park(boolean isAbsolute, long time); // 取消阻塞线程
public native void unpark(Object thread); // 获得对象锁(可重入锁)
@Deprecated
public native void monitorEnter(Object o); // 释放对象锁
@Deprecated
public native void monitorExit(Object o); // 尝试获取对象锁
@Deprecated
public native boolean tryMonitorEnter(Object o);

4.Class 相关

// 获取给定静态字段的内存地址偏移量,这个值对于给定的字段是唯一且固定不变的
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);

5.对象操作

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

6.数组相关

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

7.内存屏障

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

8.系统相关

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

三、简单使用

创建对象并修改其属性

跳过对象初始化阶段,或绕过构造器的安全检查,或实例化一个没有任何公共构造器的类。

反射可以实现相同的功能。但值得关注的是,我们可以修改任何对象,甚至没有这些对象的引用。

class User {
private String name;
private int age; public User(String name, int age) {
this.name = name;
this.age = age;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public int getAge() {
return age;
} public void setAge(int age) {
this.age = age;
} @Override
public String toString() {
return "User{" + "name='" + name + '\'' + ", age=" + age + '}';
}
} public static void main(String[] args) throws Exception {
Class userClass = User.class;
// 避开构造方法初始化对象
User user = (User) unsafe.allocateInstance(userClass); user.setAge(20);
System.out.println(user); // 修改对象成员值
Field field = User.class.getDeclaredField("age");
unsafe.putInt(user, unsafe.objectFieldOffset(field), 8);
System.out.println(user);
} private static Unsafe unsafe; static {
unsafe = reflectGetUnsafe();
} private static Unsafe reflectGetUnsafe() {
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
return (Unsafe) field.get(null);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

大数组

Java 数组大小的最大值为 Integer.MAX_VALUE。使用直接内存分配,我们创建的数组大小受限于堆大小。

使用堆外内存(off-heap memory)技术,这种方式的内存分配不在堆上,且不受 GC 管理,所以必须小心 Unsafe.freeMemory() 的使用。它也不执行任何边界检查,所以任何非法访问可能会导致 JVM 崩溃。

class SuperArray {
private final static int BYTE = 1;
private long size;
private long address;
private static Unsafe unsafe; static {
unsafe = reflectGetUnsafe();
} public static Unsafe getUnsafe() {
return unsafe;
} public long getAddress() {
return address;
} private static Unsafe reflectGetUnsafe() {
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
return (Unsafe) field.get(null);
} catch (Exception e) {
e.printStackTrace();
return null;
}
} public SuperArray(long size) {
this.size = size;
address = unsafe.allocateMemory(size * BYTE);
} public void set(long i, byte value) {
unsafe.putByte(address + i * BYTE, value);
} public int get(long idx) {
return unsafe.getByte(address + idx * BYTE);
} public long size() {
return size;
}
} public static void main(String[] args) {
int sum = 0;
long SUPER_SIZE = (long) Integer.MAX_VALUE * 2;
SuperArray array = new SuperArray(SUPER_SIZE);
System.out.println("Array size:" + array.size()); //
for (int i = 0; i < 100; i++) {
array.set((long) Integer.MAX_VALUE + i, (byte) 3);
sum += array.get((long) Integer.MAX_VALUE + i);
}
System.out.println("Sum of 100 elements:" + sum); //
SuperArray.getUnsafe().freeMemory(array.getAddress());
}

https://tech.meituan.com/2019/02/14/talk-about-java-magic-class-unsafe.html

https://ifeve.com/sun-misc-unsafe/

Java-Unsafe的更多相关文章

  1. Java Unsafe类

    参考了这篇文章:http://blog.csdn.net/aesop_wubo/article/details/7537278 <JAVA并发编程学习笔记之Unsafe类> Unsafe开 ...

  2. Java中Unsafe类详解

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

  3. Java中的sun.misc.Unsafe包

    chronicle项目:https://github.com/peter-lawrey/Java-Chronicle 这个项目是利用mmap机制来实现高效的读写数据,号称每秒写入5到20百万条数据. ...

  4. Java ConcurrentHashmap 解析

    总体描述: concurrentHashmap是为了高并发而实现,内部采用分离锁的设计,有效地避开了热点访问.而对于每个分段,ConcurrentHashmap采用final和内存可见修饰符Volat ...

  5. 浅述 Java 并发

    浅述 Java 并发 volatile volatile只能保证变量对各个线程的可见性,但不能保证原子性.关于 Java语言 volatile 的使用方法就不多说了,我的建议是 除了 配合packag ...

  6. Java CAS 原理分析

    1.简介 CAS 全称是 compare and swap,是一种用于在多线程环境下实现同步功能的机制(可以把 CAS 看做乐观锁).CAS 操作包含三个操作数 -- 内存位置.预期数值和新值.CAS ...

  7. Java 原子语义同步的底层实现

    原子语义同步的底层实现 volatile volatile只能保证变量对各个线程的可见性,但不能保证原子性.关于 Java语言 volatile 的使用方法就不多说了,我的建议是 除了 配合packa ...

  8. Java自定义cas操作

    java Unsafe工具类提供了一个方法 public final native boolean compareAndSwapObject(Object var1, long var2, Objec ...

  9. 【锁】java 锁的技术内幕

    转载自https://www.2cto.com/kf/201607/525119.html 一.基础知识 在Java并发编程里头,锁是一个非常重要的概念.就如同现实生活一样,如果房子上了锁.别人就进不 ...

  10. 关于 Netty Channel 的 Autoread

    Netty 4 的 Channel 多了一个 autoread 参数, 它的用处是在让 channel 在触发某些事件以后(例如 channelActive, channelReadComplete) ...

随机推荐

  1. HTML Ueditor图片宽度超出编辑器

    问题描述 Ueditor上传图片宽度尺寸超出编辑器宽度,显示异常 解决方案 ueditor.all.js 添加img宽度限制(搜索body{margin:8px;font-family:sans-se ...

  2. vue网络不好时不间断请求

    配置默认参数 const { apiConfig: { timeout, retry, retryDelay } } = config; if(timeout) axios.defaults.time ...

  3. JavaScript之排序算法

    一.冒泡排序 原理:1.比较相邻的元素.如果第一个比第二个大,就交换两个数:2.对每一对相邻元素重复做步骤一,从开始第一对到结尾的最后一对,该步骤结束会产生一个最大的数:3.针对所有的数重复以上的步骤 ...

  4. 简要了解web安全之sql注入

    什么是sql注入? 通俗来讲就是通过 将可执行sql语句作为参数 传入查询sql 中,在sql编译过程中 执行了传入进来的恶意 sql,从而 得到 不应该查到或者不应该执行的sql语句,对网站安全,信 ...

  5. go语言入门(8)异常处理

    1,error接口 Go语言引入了一个关于错误处理的标准模式,即error接口,它是Go语言内建的接口类型,该接口的定义如下: type error interface { Error() strin ...

  6. asp.net网站部署在云服务器windows server 2008上

    搭建一个网站需要以下4个准备: 1.域名解析 2.(云)服务器 3.数据库 4.网站代码 其中1可以可以去DNSPOD申请,同时需要进行备案,在上面就都可以完成.2用的是阿里云服务器windows s ...

  7. 【python】python _、__、__xx__之间的差别

    本文来自 yzl11 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/yzl11/article/details/53792416?utm_source=copy 单下 ...

  8. 【Git】六、分支管理&冲突解决

    上一节讲了如何和远端的仓库协同工作,这一节介绍一下分支 ---------------------------- 提要 //创建一个分支dev $ git branch dev //切换到dev分支 ...

  9. JavaMaven【八、pom.xml】

    简介: 重点学习: 1.dependency-scope 依赖范围 compile 编译 默认,对编译.测试.运行都有效 provided 编译和测试时有效 runtime 测试和运行时有效 test ...

  10. 异步处理的框架Sanic的使用方法和小技巧

    Sanic是异步处理的框架,运用Sanic可以开发快速异步响应的web程序.想必大家看到这个都会比较期待和兴奋. 那么如何使用Sanic来实现快速响应呢?我们先来看一看Sanic的基本介绍. Sani ...