Java-Unsafe
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的更多相关文章
- Java Unsafe类
参考了这篇文章:http://blog.csdn.net/aesop_wubo/article/details/7537278 <JAVA并发编程学习笔记之Unsafe类> Unsafe开 ...
- Java中Unsafe类详解
http://www.cnblogs.com/mickole/articles/3757278.html Java不能直接访问操作系统底层,而是通过本地方法来访问.Unsafe类提供了硬件级别的原子操 ...
- Java中的sun.misc.Unsafe包
chronicle项目:https://github.com/peter-lawrey/Java-Chronicle 这个项目是利用mmap机制来实现高效的读写数据,号称每秒写入5到20百万条数据. ...
- Java ConcurrentHashmap 解析
总体描述: concurrentHashmap是为了高并发而实现,内部采用分离锁的设计,有效地避开了热点访问.而对于每个分段,ConcurrentHashmap采用final和内存可见修饰符Volat ...
- 浅述 Java 并发
浅述 Java 并发 volatile volatile只能保证变量对各个线程的可见性,但不能保证原子性.关于 Java语言 volatile 的使用方法就不多说了,我的建议是 除了 配合packag ...
- Java CAS 原理分析
1.简介 CAS 全称是 compare and swap,是一种用于在多线程环境下实现同步功能的机制(可以把 CAS 看做乐观锁).CAS 操作包含三个操作数 -- 内存位置.预期数值和新值.CAS ...
- Java 原子语义同步的底层实现
原子语义同步的底层实现 volatile volatile只能保证变量对各个线程的可见性,但不能保证原子性.关于 Java语言 volatile 的使用方法就不多说了,我的建议是 除了 配合packa ...
- Java自定义cas操作
java Unsafe工具类提供了一个方法 public final native boolean compareAndSwapObject(Object var1, long var2, Objec ...
- 【锁】java 锁的技术内幕
转载自https://www.2cto.com/kf/201607/525119.html 一.基础知识 在Java并发编程里头,锁是一个非常重要的概念.就如同现实生活一样,如果房子上了锁.别人就进不 ...
- 关于 Netty Channel 的 Autoread
Netty 4 的 Channel 多了一个 autoread 参数, 它的用处是在让 channel 在触发某些事件以后(例如 channelActive, channelReadComplete) ...
随机推荐
- HTML Ueditor图片宽度超出编辑器
问题描述 Ueditor上传图片宽度尺寸超出编辑器宽度,显示异常 解决方案 ueditor.all.js 添加img宽度限制(搜索body{margin:8px;font-family:sans-se ...
- vue网络不好时不间断请求
配置默认参数 const { apiConfig: { timeout, retry, retryDelay } } = config; if(timeout) axios.defaults.time ...
- JavaScript之排序算法
一.冒泡排序 原理:1.比较相邻的元素.如果第一个比第二个大,就交换两个数:2.对每一对相邻元素重复做步骤一,从开始第一对到结尾的最后一对,该步骤结束会产生一个最大的数:3.针对所有的数重复以上的步骤 ...
- 简要了解web安全之sql注入
什么是sql注入? 通俗来讲就是通过 将可执行sql语句作为参数 传入查询sql 中,在sql编译过程中 执行了传入进来的恶意 sql,从而 得到 不应该查到或者不应该执行的sql语句,对网站安全,信 ...
- go语言入门(8)异常处理
1,error接口 Go语言引入了一个关于错误处理的标准模式,即error接口,它是Go语言内建的接口类型,该接口的定义如下: type error interface { Error() strin ...
- asp.net网站部署在云服务器windows server 2008上
搭建一个网站需要以下4个准备: 1.域名解析 2.(云)服务器 3.数据库 4.网站代码 其中1可以可以去DNSPOD申请,同时需要进行备案,在上面就都可以完成.2用的是阿里云服务器windows s ...
- 【python】python _、__、__xx__之间的差别
本文来自 yzl11 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/yzl11/article/details/53792416?utm_source=copy 单下 ...
- 【Git】六、分支管理&冲突解决
上一节讲了如何和远端的仓库协同工作,这一节介绍一下分支 ---------------------------- 提要 //创建一个分支dev $ git branch dev //切换到dev分支 ...
- JavaMaven【八、pom.xml】
简介: 重点学习: 1.dependency-scope 依赖范围 compile 编译 默认,对编译.测试.运行都有效 provided 编译和测试时有效 runtime 测试和运行时有效 test ...
- 异步处理的框架Sanic的使用方法和小技巧
Sanic是异步处理的框架,运用Sanic可以开发快速异步响应的web程序.想必大家看到这个都会比较期待和兴奋. 那么如何使用Sanic来实现快速响应呢?我们先来看一看Sanic的基本介绍. Sani ...