Java中的Unsafe的介绍与使用
相关文章
美团-Unsafe
JavaGuide-Unsafe
什么是Unsafe???
如何创建Unsafe对象?
通过反射获取Unsafe对象(案例)
Unsafe功能简介
1. 内存操作
2. 内存屏障
3. 对象操作
4. 数据操作
5. CAS 操作
6. 线程调度
7. Class 操作
8. 系统信息
Java中的Unsafe的介绍与使用
相关文章
美团-Unsafe
Java魔法类:Unsafe应用解析 - 美团技术团队 (meituan.com)https://tech.meituan.com/2019/02/14/talk-about-java-magic-class-unsafe.html
JavaGuide-Unsafe
Java 魔法类 Unsafe 详解 (javaguide.cn)https://javaguide.cn/java/basis/unsafe.html#unsafe-%E4%BB%8B%E7%BB%8D
什么是Unsafe???
Unsafe是位于sun.misc包下的一个类,主要提供一些用于执行低级别、不安全操作的方法,如直接访问系统内存资源、自主管理内存资源等,这些方法在提升Java运行效率、增强Java语言底层资源操作能力方面起到了很大的作用。
但由于Unsafe类使Java语言拥有了类似C语言指针一样操作内存空间的能力,这无疑也增加了程序发生相关指针问题的风险。
在程序中过度、不正确使用Unsafe类会使得程序出错的概率变大,使得Java这种安全的语言变得不安全,因此对Unsafe的使用一定要慎重。
如何创建Unsafe对象?
我们无法直接通过new来实例化一个Unsafe对象。也不能通过 Unsafe.getUnsafe() 的方式,运行的时候会抛异常的。
我们只能通过反射的方式来创建这个对象(还有一种方式,“-Xbootclasspath/a”,但是没必要)
通过反射获取Unsafe对象(案例)
通过Unsafe获取内存中的偏移量
public class UnsafeDemo {
public static void main(String[] args) {
try {
// 返回这个类中的指定字段的Field对象
Field field = Unsafe.class.getDeclaredField("theUnsafe");
// true: 反射的对象在使用时抑制Java语言访问检查
field.setAccessible(true);
// 字段不是静态字段,要传入反射类的对象,如果传null是会报 java.lang.NullPointerException!
// 字段是静态字段,传入任何对象都是可以的,包括null
Unsafe unsafe = (Unsafe) field.get(field);
// 返回对象成员属性在内存地址相对于此对象的内存地址的偏移量
long nameOffset = unsafe.objectFieldOffset(Test.class.getDeclaredField("name"));
long ageOffset = unsafe.objectFieldOffset(Test.class.getDeclaredField("age"));
System.out.println(nameOffset); // 12
System.out.println(ageOffset); //16
} catch (Exception e) {
e.printStackTrace();
}
}
}
class Test{
private String name;
private Integer age;
public Test(String name, Integer age) {
this.name = name;
this.age = age;
}
}
Unsafe unsafe = (Unsafe) field.get(field)
在java的反射中,通过字段获取对象:
- 字段不是静态字段,要传入反射类的对象,如果传null是会报java.lang.NullPointerException
- 字段是静态字段,传入任何对象都是可以的,包括null !
Unsafe功能简介
概括的来说,Unsafe 类实现功能可以被分为下面 8 类。
这里只是对API进行罗列,具体的使用可以参考:
Java 魔法类 Unsafe 详解 (javaguide.cn)https://javaguide.cn/java/basis/unsafe.html#unsafe-%E5%8A%9F%E8%83%BD
1. 内存操作
我们都知道Java不可以直接对内存进行操作,对象内存的分配和回收都是由JVM帮助我们实现的。
但是Unsafe为我们在Java中提供了直接操作内存的能力。
//分配新的本地空间
public native long allocateMemory(long bytes);
//重新调整内存空间的大小
public native long reallocateMemory(long address, long bytes);
//将内存设置为指定值
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);
//清除内存
public native void freeMemory(long address);
2. 内存屏障
内存屏障可以看做对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作。
以 loadFence()方法为例,它会禁止读操作重排序,保证在这个屏障之前的所有读操作都已经完成,并且将缓存数据设为无效,重新从主存中进行加载。
//内存屏障,禁止load操作重排序。屏障前的load操作不能被重排序到屏障后,屏障后的load操作不能被重排序到屏障前
public native void loadFence();
//内存屏障,禁止store操作重排序。屏障前的store操作不能被重排序到屏障后,屏障后的store操作不能被重排序到屏障前
public native void storeFence();
//内存屏障,禁止load、store操作重排序
public native void fullFence();
3. 对象操作
//在对象的指定偏移地址获取一个对象引用
public native Object getObject(Object o, long offset);
//在对象指定偏移地址写入一个对象引用
public native void putObject(Object o, long offset, Object x);
//在对象的指定偏移地址处读取一个int值,支持volatile load语义
public native int getIntVolatile(Object o, long offset);
//在对象指定偏移地址处写入一个int,支持volatile store语义
public native void putIntVolatile(Object o, long offset, int x);
// 有序写入
public native void putOrderedObject(Object o, long offset, Object x);
public native void putOrderedInt(Object o, long offset, int x);
public native void putOrderedLong(Object o, long offset, long x);
4. 数据操作
//返回数组中第一个元素的偏移地址
public native int arrayBaseOffset(Class<?> arrayClass);
//返回数组中一个元素占用的大小
public native int arrayIndexScale(Class<?> arrayClass);
5. 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);
6. 线程调度
方法 park、unpark 即可实现线程的挂起与恢复,将一个线程进行挂起是通过 park 方法实现的,调用 park 方法后,线程将一直阻塞直到超时或者中断等条件出现。
unpark 可以终止一个挂起的线程,使其恢复正常。
//取消阻塞线程
public native void unpark(Object thread);
//阻塞线程
public native void park(boolean isAbsolute, long time);
//获得对象锁(可重入锁)
@Deprecated
public native void monitorEnter(Object o);
//释放对象锁
@Deprecated
public native void monitorExit(Object o);
//尝试获取对象锁
@Deprecated
public native boolean tryMonitorEnter(Object o);
7. Class 操作
//获取静态属性的偏移量
public native long staticFieldOffset(Field f);
//获取静态属性的对象指针
public native Object staticFieldBase(Field f);
//判断类是否需要实例化(用于获取类的静态属性前进行检测)
public native boolean shouldBeInitialized(Class<?> c);
8. 系统信息
//返回系统指针的大小。返回值为4(32位系统)或 8(64位系统)。
public native int addressSize();
//内存页的大小,此值为2的幂次方。
public native int pageSize();