Unsafe 完全解析

  Unsafe类内提供了众多直接进行底层操作的方法,包括对象内存操作、非堆内存管理、CAS等,由于这些方法均采用JNI方式实现,Java通过C++等更贴近底层的语言实现,所以在性能上可以取得更好的效果。与此同时,一些操作游离在JVM体系之外,GC等机制无法有效管理Unsafe API产生的影响,因而Unsafe类是不安全的,并不建议使用该类。

  Unsafe 实例化

  通过查看Unsafe源码,可以看出:

  Unsafe类本身将构造函数私有化,无法通过正常的方式进行实例化:

private Unsafe() {}

  内部提供了依赖单例模式获取实例的静态方法:

private static final Unsafe theUnsafe = new Unsafe();

@CallerSensitive
public static Unsafe getUnsafe() {
	Class<?> caller = Reflection.getCallerClass();
	if (!VM.isSystemDomainLoader(caller.getClassLoader()))
		throw new SecurityException("Unsafe");
	return theUnsafe;
}

  如上源码所示,Unsafe虽然提供了获取实例的方法,但同时限定调用该方法的类必须是通过引导加载器(bootstrap classloader)加载的才可以,否则抛出SecurityException。

  虽然通过常规方式无法获取Unsafe实例,可以使用反射获取theUnsafe属性进而获得Unsafe实例:

Field field = null;
Unsafe unsafe = null;

field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
unsafe = (Unsafe) field.get(null);

  Unsafe 方法

  Unsafe提供了众多的方法,截止JDK1.8版本共85个方法,下面按照类中出现的顺序,对这些方法进行分析并演示用法。

  为了方便后续方法演示,新建一个类,提供几个属性,如下:

package com.securitit.serialize.unsafe;

public class TestObject {
	// static boolean.
	public static boolean isInit = false;
    // volatile int.
    public volatile int order;
	// string.
	private String name;
	// double.
	private double desc;
	// int.
	private int length = 12;
	// Constructor.
	public TestObject() {
        this.order = 1001;
		this.name = "name";
		this.desc = 99.99;
		this.length = 100;
	}

}

​  获取对象属性偏移量:

扫描二维码关注公众号,回复: 11363168 查看本文章

  Unsafe中存在大量依赖偏移量的操作,首先来看下Unsafe提供的获取偏移量的方法:

// 获取静态属性偏移量.
public native long staticFieldOffset(Field f);
// 获取实例属性偏移量.
public native long objectFieldOffset(Field f);
// 获取静态属性所属类对象.
public native Object staticFieldBase(Field f);
// 以下是获取数组的偏移量,数组可以像实例一样读取/赋值.
// 获取数组偏移量.
public native int arrayBaseOffset(Class<?> arrayClass);
// 获取数字单个位置长度.
public native int arrayIndexScale(Class<?> arrayClass);

  演示示例:

Field isInitField = TestObject.class.getDeclaredField("isInit");
Field lengthField = TestObject.class.getDeclaredField("length");

System.out.println(unsafe.staticFieldOffset(isInitField));
System.out.println(unsafe.objectFieldOffset(lengthField));
System.out.println(unsafe.staticFieldBase(isInitField));

  输出结果:

104
12
class com.securitit.serialize.unsafe.TestObject

  读取/设置对象指定偏移位置的值:

  可操作数据类型int、Object、boolean、byte、short、char、long、float、double。同时数组也可以使用此种方式进行取值和赋值,数组属于Object类型。

// 实例普通属性操作.
public native int getInt(Object o, long offset);
public native void putInt(Object o, long offset, int x);
public native Object getObject(Object o, long offset);
public native void putObject(Object o, long offset, Object x);
public native boolean getBoolean(Object o, long offset);
public native void putBoolean(Object o, long offset, boolean x);
public native byte getByte(Object o, long offset);
public native void putByte(Object o, long offset, byte x);
public native short getShort(Object o, long offset);
public native void putShort(Object o, long offset, short x);
public native char getChar(Object o, long offset);
public native void putChar(Object o, long offset, char x);
public native long getLong(Object o, long offset);
public native void putLong(Object o, long offset, long x);
public native float getFloat(Object o, long offset);
public native void putFloat(Object o, long offset, float x);
public native double getDouble(Object o, long offset);
public native void putDouble(Object o, long offset, double x);
// 实例volatile类型属性操作.
public native Object getObjectVolatile(Object o, long offset);
public native void putObjectVolatile(Object o, long offset, Object x);
public native int getIntVolatile(Object o, long offset);
public native void putIntVolatile(Object o, long offset, int x);
public native boolean getBooleanVolatile(Object o, long offset);
public native void putBooleanVolatile(Object o, long offset, boolean x);
public native byte getByteVolatile(Object o, long offset);
public native void putByteVolatile(Object o, long offset, byte x);
public native short etShortVolatile(Object o, long offset);
public native void putShortVolatile(Object o, long offset, short x);
public native char getCharVolatile(Object o, long offset);
public native void putCharVolatile(Object o, long offset, char x);
public native long getLongVolatile(Object o, long offset);
public native void  putLongVolatile(Object o, long offset, long x);
public native float getFloatVolatile(Object o, long offset);
public native void putFloatVolatile(Object o, long offset, float x);
public native double getDoubleVolatile(Object o, long offset);
public native void putDoubleVolatile(Object o, long offset, double x);
// 惰性赋值,通常出现在AtomicXXX.lazySet,它允许volatile变量和后续的内存操作重排序,
// 就像普通非volatile变量的写操作,所以其他线程可能不能立即获取到新的值.
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);

  这些方法都很类似,下面使用int数据类型作为演示:

TestObject testObject = new TestObject();

Field lengthField = TestObject.class.getDeclaredField("length");
Field orderField = TestObject.class.getDeclaredField("order");
		
// 获取普通属性值.
System.out.println(unsafe.getInt(testObject, unsafe.objectFieldOffset(lengthField)));
// 获取volatile类型属性值.
System.out.println(unsafe.getIntVolatile(testObject,unsafe.objectFieldOffset(orderField)));
// 数组操作.
String[] strArr = new String[] { "a", "v", "v" };
long i = unsafe.arrayBaseOffset(String[].class);
long scale = unsafe.arrayIndexScale(String[].class);
// 获取数组第一个位置的值.
System.out.println(unsafe.getObject(strArr, i));
// 修改第一个位置的值.
unsafe.putObject(strArr, i + scale * 0, "d");
// 重新获取第一个位置的值.
System.out.println(unsafe.getObject(strArr, i + scale * 0));

  输出结果:

100
1001
a
d

  TestObject类构造方法中,对实例属性进行赋值操作,输出结果正是构造方法中所赋的值。

  堆外内存管理:

  Unsafe提供了针对内存的操作,内存分配以及内存操作。主要涉及以下几个方法:

// 内存分配.
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);
// 针对allocateMemory和reallocateMemory分配后获得的内存地址根据数据类型进行读取/设置操作.
public native byte getByte(long address);
public native void putByte(long address, byte x);
public native short getShort(long address);
public native void putShort(long address, short x);
public native char getChar(long address);
public native void putChar(long address, char x);
public native int getInt(long address);
public native void putInt(long address, int x);
public native long getLong(long address);
public native void putLong(long address, long x);
public native float getFloat(long address);
public native void putFloat(long address, float x);
public native double stDouble(long address);
public native void putDouble(long address, double x);
public native long getAddress(long address);
public native void putAddress(long address, long x);

  getXXX和putXXX操作类似,我们同样以int类型为例:

// 分配一个8byte的内存.
long address = unsafe.allocateMemory(8L);
// 初始化各个byte为1.
unsafe.setMemory(address, 8L, (byte) 1);
// 结果输出.
System.out.println(unsafe.getInt(address));
// 设置0-3的4个byte为0x7fffffff.0x7fffffff为int正数最大值.
unsafe.putInt(address, 0x7fffffff);
// 设置4-7的4个byte为0x80000000.0x80000000为int负数最小值.
unsafe.putInt(address + 4, 0x80000000);
// 打印设置的值.
System.out.println(unsafe.getInt(address));
System.out.println(unsafe.getInt(address + 4));

  输出结果:

16843009
2147483647
-2147483648

  正如设置时预想的值一致,通过这种方式,可以动态分配内存,且可以通过地址空间进行操作。

  提供对象属性CAS操作:

  提供对象属性CAS操作,可以针对Oject、int、long类型操作,若不是很了解CAS,可以参照前一篇CAS文章:

public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object x);
public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x);
public final native boolean compareAndSwapLong(Object o, long offset, long expected, long x);

  以int类型操作为例:

TestObject testObject = new TestObject();

Field lengthField = TestObject.class.getDeclaredField("length");
long address = unsafe.objectFieldOffset(lengthField);
unsafe.compareAndSwapInt(testObject, address, 100, 300);

System.out.println(unsafe.getInt(testObject, unsafe.objectFieldOffset(lengthField)));

  输出结果:

300

  从运行结果可知,TestObject初始值为200,经过compareAndSwapInt后成功将值修改为300。

  类加载操作:

// 判断指定类是否需要初始化.
public native boolean shouldBeInitialized(Class<?> c);
// 保证指定类已经初始化.
public native void ensureClassInitialized(Class<?> c);
// 用于加载类,并创建类.
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);
// 用于创建一个类,但不会调用构造方法,如果这个类还未初始化,则初始化这个类.
public native Object allocateInstance(Class<?> cls) throws InstantiationException;

  同步机制:

​  synchronized关键实现的同步机制,就是利用这两个方法实现的,具体使用也是非常简单,类似JUC中Lock的使用,在需要加锁的代码处调用monitorEnter或tryMonitorEnter,处理完成后,调用monitorExit解锁。

// 加锁:获取指定实例的监视器锁.
public native void monitorEnter(Object o);
// 解锁:释放指定实例的监视器锁.
public native void monitorExit(Object o);
// 尝试加锁.
public native boolean tryMonitorEnter(Object o);

  演示示例:

Object monitor = new Object();
unsafe.monitorEnter(monitor);
System.out.println("monitor.");
unsafe.monitorExit(monitor);

  线程挂起:

  JUC中,LockSupport类中park和unpark就是借助Unsafe的park和unpark实现的,具体使用很简单,就不详细介绍了。

// 唤醒指定线程.
public native void unpark(Object thread);
// 挂起当前线程.
public native void park(boolean isAbsolute, long time);

  内存屏障:

  Java中,内置的synchronized和volatile在指令级都是通过内存屏障实现的,内存屏障会禁止编译器、内存、指令重排序,来保证程序在多线程环境下的语义正确性。

// 保证在这个屏障之前的所有读操作都已经完成.
public native void loadFence();
// 保证在这个屏障之前的所有写操作都已经完成.
public native void storeFence();
// 保证在这个屏障之前的所有读写操作都已经完成.
public native void fullFence();

  内存信息:

  获取当前计算机内存方面配置:

// 本地指针大小,4或8,分别代表32位与64位.
public native int addressSize();
// 内存页面大小.通常为4KB,即4096.
public native int pageSize();

  本人机器内存信息如下,在没有改动的情况下,这些配置都是默认值:

8
4096

其他方法:

// 绕过检测机制抛出异常.
public native void throwException(Throwable ee);
// 获取系统的平均负载值,loadavg 这个 double 数组将会存放负载值的结果,nelems 决定样本数量,
// nelems 只能取值为 1 到 3,分别代表最近 1、5、15 分钟内系统的平均负载.
// 如果无法获取系统的负载,此方法返回 -1,否则返回获取到的样本数量(loadavg 中有效的元素个数).
public native int getLoadAverage(double[] loadavg, int nelems);

  关于Unsafe的全部内容都在这里了,Unsafe虽然功能十分强大,但是并不建议在日常开发中使用它,主要原因就是它的不确定性。若要使用Unsafe,尽量在更底层、更通用的层次使用。

  注:文中源码均来自于JDK1.8版本,不同版本间可能存在差异。

  如果有哪里有不明白或不清楚的内容,欢迎留言哦!

猜你喜欢

转载自blog.csdn.net/securitit/article/details/106753352