Java 不能直接访问操作系统底层,而是通过本地方法来访问。Unsafe 类提供了硬件级别的原子操作。Unsafe 类使用 private 修饰构造方法,只能使用他自己提供的一个 final 类来进行获取。
private static native void registerNatives();
static {
registerNatives();
sun.reflect.Reflection.registerMethodsToFilter(Unsafe.class, "getUnsafe");
}
private Unsafe() {}
private static final Unsafe theUnsafe = new Unsafe();
//获取unsafe实例的单例方法
@CallerSensitive
public static Unsafe getUnsafe() {
Class<?> caller = Reflection.getCallerClass();
//只有由主类加载器(BootStrap classLoader)加载的类才能调用这个类中的方法
if (!VM.isSystemDomainLoader(caller.getClassLoader()))
throw new SecurityException("Unsafe");
return theUnsafe;
}
Unsafe 类提供 102 个 native 方法,主要包含类、对象、变量的管理(获取和修改),内存管理,线程管理,内存屏障等。
public static Unsafe getUnsafe() throws Exception{
//1.最简单的使用方式是基于反射获取Unsafe实例
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);
return unsafe;
}
public native Object getObject(Object o, long offset);
该方法用于获取 Java 对象 o 中内存地址偏移量为 offerset 的对象。类似的方法还有 getInt()、getDouble 等。
Object o:引用对象 long offset:内存偏移量(偏移量是可以通过 objectFieldOffset 方法获取获取的)
public native long objectFieldOffset(Field f);
public native long staticFieldOffset(Field f);
public native int arrayBaseOffset(Class<?> arrayClass);
操作案例
@Data
public class UnsafeTest {
//类占8位
private int id;//占4位
private String sex;//对象占4位
private String name;//offset=20
public static Unsafe getUnsafe() throws Exception{
//1.最简单的使用方式是基于反射获取Unsafe实例
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);
return unsafe;
}
public static void main(String[] args) throws Exception {
Unsafe unsafe = getUnsafe();
UnsafeTest hello = new UnsafeTest();
hello.name = "mikey";
Field field = UnsafeTest.class.getDeclaredField("name");
long offset = unsafe.objectFieldOffset(field);
System.out.println("offset="+offset);
Object name = unsafe.getObject(hello, offset);
System.out.println(name);
}
}
该方法用于修改 Java 对象 o 中内存地址偏移量为 offerset 的对象。类似的方法还有 putInt()、putDouble 等。
public native void putObject(Object o, long offset, Object x);
Object o:待更新的对象 long offset:对象熟悉的偏移地址 Object x:更新后的值
@Data
public class UnsafeTest {
//类占8位
private int id;//占4位
private String sex;//对象占4位
private String name;//offset=20
public static Unsafe getUnsafe() throws Exception{
//1.最简单的使用方式是基于反射获取Unsafe实例
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);
return unsafe;
}
public static void main(String[] args) throws Exception {
Unsafe unsafe = getUnsafe();
UnsafeTest hello = new UnsafeTest();
hello.name = "mikey";
Field field = UnsafeTest.class.getDeclaredField("name");
long offset = unsafe.objectFieldOffset(field);
System.out.println("offset="+offset);
Object name = unsafe.getObject(hello, offset);
System.out.println(name);
unsafe.putObject(hello,offset,"leo");
System.out.println(hello.name);
}
}
getObjectVolatile 方法用于获取对象 o 带有 volatile 关键字修饰的属性,和 getObject 方法类似。
Object getObjectVolatile(Object o, long offset);
putObjectVolatile 方法用于设置对象 o 带有 volatile 关键字修饰的属性,和 putObject 方法类似。
/**
* Stores a reference value into a given Java variable, with
* volatile store semantics. Otherwise identical to putObject(Object, long, Object)
*/
public native void putObjectVolatile(Object o, long offset, Object x);
putObjectVolatile(Object,long,Object)的版本,不保证存储对其他线程的即时可见性。通常只有当底层字段是 volatile(或者如果是数组单元,则只能使用 volatile 访问)时,此方法才有用。
public native void putOrderedObject(Object o, long offset, Object x);
告诉 VM 定义一个类,而不进行安全检查。默认情况下类加载器和保护域来自调用方的类。
public native Class<?> defineClass(String name, byte[] b, int off, int len, ClassLoader loader, ProtectionDomain protectionDomain);
定义一个类,但不告知类加载器和系统字典。类似于我们代码中的 lambda 表达式。
public native Class<?> defineAnonymousClass(Class<?> hostClass, byte[] data, Object[] cpPatches);
结合 staticFieldOffset 返回静态字段的位置。获取基“Object”(如果有),通过它可以通过 getInt(Object,long)等方法访问给定类的静态字段。此值可能为空。此值可能引用一个“cookie”对象,不能保证它是真实对象,并且除了用作此类中 get 和 put 例程的参数外,不应以任何方式使用它。
public native Object staticFieldBase(Field f);
检测给定类是否需要初始化。这通常是需要与获取类别。仅当对 ensureClassifilized 的调用无效时返回 false。
public native boolean shouldBeInitialized(Class<?> c);
该方法用于确保给定类已初始化。这通常需要与获取类的静态字段基一起使用。
public native void ensureClassInitialized(Class<?> c);
public native long objectFieldOffset(Field f);//获取对象的字段(属性)偏移地址
public native long staticFieldOffset(Field f);//获取对象的静态字段(属性)偏移地址
public native int arrayBaseOffset(Class<?> arrayClass);//获取数组的字段(属性)偏移地址
public native int arrayIndexScale(Class<?> arrayClass);//可以获取数组中元素间的偏移地址增量
获取本机指针字节大小。该值将为 4 或 8。
public native int addressSize();
获取本地内存的页数,该值为 2 的幂次方
public native int pageSize();
从给定内存地址获取本机指针。如果地址为零,或未指向从 allocateMemory 获得的块,则结果未定义。如果本机指针的宽度小于 64 位,则会将其作为无符号数扩展到 Java long。指针可以通过任何给定的字节偏移量进行索引,只需将该偏移量(作为简单整数)添加到表示指针的 long 中即可。从目标地址实际读取的字节数可能由地址大小决定。
public native long getAddress(long address);
public native void putAddress(long address, long x);
通过 Unsafe 类可以分配内存,可以释放内存;
类中提供的 3 个本地方法allocateMemory、reallocateMemory、freeMemory分别用于分配内存,扩充内存和释放内存,与 C 语言中的 3 个方法对应。
分配一个新的给定大小本地内存。内存内容未初始化;它们通常是垃圾。生成的本机指针永远不会为零,并且将针对所有值类型进行对齐。通过调用 freeMemory 来释放此内存,或使用 reallocatemory 调整其大小。
public native long allocateMemory(long bytes);
将新的本机内存块大小调整为给定的字节大小。这个超过旧块大小的新块的内容为未初始化;它们通常是垃圾。生成的本机当且仅当请求的大小为零时,指针将为零。这个生成的本机指针将针对所有值类型进行对齐。处置通过调用 freeMemory 或使用 reallocateMemory 调整其大小。其中传递给此方法的 address 可能为 null 在这种情况下,将执行分配。
public native long reallocateMemory(long address, long bytes);
将给定内存块中的所有字节设置为固定值(通常为零)。这提供了单寄存器寻址模式。
public void setMemory(long address, long bytes, byte value)
将给定内存块中的所有字节设置为另一个块的副本。此方法通过两个参数确定每个块的基址,因此它(实际上)提供双寄存器寻址模式,如{getInt(Object,long)}中所述。当对象引用为 null 时,偏移量提供一个绝对基址。传输以确定大小的相干(原子)单位进行通过地址和长度参数。如果有效地址和长度均为偶数模 8,传输以“长”单位进行。如果有效地址和长度分别为偶模 4 或 2,传输以“int”或“short”为单位进行。
public native void copyMemory(Object srcBase, long srcOffset,Object destBase, long destOffset,long bytes);
释放 address 地址的内存空间,如果 address 为空则不做任何处理
public native void freeMemory(long address);
线程管理主要提供了对象锁,cas 轻量级锁等功能
锁住当前对象 o,它必须通过{monitorExit 方法}解锁。可重入锁。
public native void monitorEnter(Object o);
解锁当前对象
public native void monitorExit(Object o);
尝试锁定对象。返回 true 或 false 以指示锁定是否成功。如果是,则对象必须是通过{monitorExit}解锁。
public native boolean tryMonitorEnter(Object o);
锁住/解锁当前线程。LockSupport 类就是基于这两个方法实现的。
park:除非许可可用,否则出于线程调度目的禁用当前线程。如果许可证用,则会使用该许可并立即返回调用;否则,出于线程调度目的,当前线程将被禁用,并处于休眠状态,直到发生以下三种情况之一:
其他一些线程以当前线程为目标调用 unpark;其他线程中断当前线程;该调用错误地(即无原因地)返回。此方法不报告导致该方法返回的原因。调用者应该首先重新检查导致线程停止的条件。例如,调用者还可以在返回时确定线程的中断状态。
//第一个参数是是否是绝对时间,第二个参数是等待时间值。如果isAbsolute是true则会实现ms定时。如果isAbsolute是false则会实现ns定时。
public native void park(boolean isAbsolute, long time);
//如果线程在park上被阻塞,那么它将解除阻塞。否则,它对park的下一个调用将保证不会阻塞。如果给定的线程尚未启动,则不能保证此操作有任何效果。
public native void unpark(Object thread);
CAS(compareAndSwapObject 比较并交换)
public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object x);
针对 Object 对象进行 CAS 操作。即是对应 Java 变量引用 o,原子性地更新 o 中偏移地址为 offset 的属性的值为 x,当且仅的偏移地址为 offset 的属性的当前值为 expected 才会更新成功返回 true,否则返回 false。
o:目标 Java 变量引用。offset:目标 Java 变量中的目标属性的偏移地址。expected:目标 Java 变量中的目标属性的期望的当前值。x:目标 Java 变量中的目标属性的目标更新值。类似的方法有compareAndSwapInt和compareAndSwapLong,在 Jdk8 中基于 CAS 扩展出来的方法有getAndAddInt、getAndAddLong、getAndSetInt、getAndSetLong、getAndSetObject,它们的作用都是:通过 CAS 设置新的值,返回旧的值。
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
UnsafeWrapper("Unsafe_CompareAndSwapInt");
oop p = JNIHandles::resolve(obj);
//获取对象的变量的地址
jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
//调用Atomic操作
//进入atomic.hpp,大意就是先去获取一次结果,如果结果和现在不同,就直接返回,因为有其他人修改了;否则会一直尝试去修改。直到成功。
return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END
在 Unsafe 类中提供了三个内存屏障的方法,storeFence、fullFence、loadFence
表示该方法之前的所有 store(写)操作在内存屏障之前完成。
public native void storeFence();
表示该方法之前的所有 load(读)操作在内存屏障之前完成。
public native void loadFence();
表示该方法之前的所有 load、store 操作在内存屏障之前完成
public native void fullFence();
资料 http://mishadoff.com/blog/java-magic-part-4-sun-dot-misc-dot-unsafe/ jdk1.8 sun.misc.Unsafe