简介
-
Unsafe类具有私有构造函数,并且是单例的。虽然Unsafe具有静态的getUnsafe()方法,但是直接调用Unsafe.getUnsafe(),则可能会收到SecurityException,此方法仅可被用于用于受信任的代码。如果代码是受信任的,会判断是不是系统类加载器
@CallerSensitive public static Unsafe getUnsafe() { Class<?> caller = Reflection.getCallerClass(); if (!VM.isSystemDomainLoader(caller.getClassLoader())) throw new SecurityException("Unsafe"); return theUnsafe; }
-
代码变为受信任的的方式
1. 增加JVM虚拟机选项 java -Xbootclasspath:/usr/jdk1.7.0/jre/lib/rt.jar: ${path} // 其中path为调用Unsafe相关方法的类所在jar包路径
-
通过反射获取Unsafe
Field f = Unsafe.class.getDeclaredField("theUnsafe"); f.setAccessible(true); Unsafe unsafe = (Unsafe) f.get(null);
方法
-
Unsafe大部分方法都是native方法,分为几类
-
Info:主要返回系统相关信息的方法
//返回系统指针的大小。返回值为4(32位系统)或 8(64位系统)。 public native int addressSize(); //内存页的大小,此值为2的幂次方。 public native int pageSize(); //8 System.out.println(unsafe.addressSize()); //4096 System.out.println(unsafe.pageSize());
-
Objects:提供对象及其字段操作的方法
//返回对象成员属性在内存地址相对于此对象的内存地址的偏移量 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; 1. 创建User类 class User { private int age; private String name; ...省略... } 2. 实例化User User user = (User) unsafe.allocateInstance(User.class); user.setAge(18); user.setName("jannal"); /** * age:对应的内存偏移地址12 * name:对应的内存偏移地址16 */ for (Field field : User.class.getDeclaredFields()) { System.out.println(field.getName() + ":对应的内存偏移地址" + unsafe.objectFieldOffset(field)); }
-
Classes:提供用于类和静态字段操作的方法
//获取给定静态字段的内存地址偏移量,这个值对于给定的字段是唯一且固定不变的 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);
-
Array:数组操作方法
//返回数组中第一个元素的偏移地址 public native int arrayBaseOffset(Class<?> arrayClass); //返回数组中一个元素占用的大小 public native int arrayIndexScale(Class<?> arrayClass);
-
Synchronization:主要提供低级别同步原语
//取消阻塞线程 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); /*** * @param obj 包含要修改field的对象 * @param offset obj中整型field的偏移量 * @param expect 期望field中存在的值 * @param update 如果期望值expect与field的当前值相同,设置filed的值为这个新值 * @return true 如果field的值被更改返回true */ public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x); public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object update); public final native boolean compareAndSwapLong(Object o, long offset, long expected, long update);
-
Memory:直接内存访问方法,绕过JVM堆直接操纵本地内存
//分配内存, 相当于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);
方法详解
allocateInstance
-
如果要跳过对象初始化或者绕过构造函数的安全检查或者类没有public的构造函数时,可以使用
allocateInstance
public class Person { private long age; public Person() { this.age = 10; } public long age() { return age; } } @Test public void testAllocateInstance() throws Exception { Unsafe unsafe = unsafe(); Person person = new Person(); //输出10 System.out.println(person.age()); person = Person.class.newInstance(); //输出10 System.out.println(person.age()); person = (Person) unsafe.allocateInstance(Person.class); //输出0 System.out.println(person.age()); }
抛出异常
-
如果不喜欢Checked Exception,可以使用如下方式,此方法抛出检查异常,但是代码没有被强制捕获或重新抛出它,就像运行时异常一样
getUnsafe().throwException(new IOException());
SizeOf
-
使用
objectFieldOffset()
方法,我们可以实现C样式的sizeof
函数。此实现返回对象的浅层大小( shallow size of object)/** * 实际上,为了获得良好,安全和准确的sizeof函数,最好使用java.lang.instrument包, * 但是它需要在JVM中指定agent选项。 */ public static long sizeOf(Object o) { Unsafe unsafe = null; try { unsafe = unsafe(); } catch (Exception e) { throw new RuntimeException(e.getMessage(), e); } HashSet<Field> fields = new HashSet<Field>(); Class c = o.getClass(); //遍历当前类以及超类所有非静态字段 while (c != Object.class) { for (Field f : c.getDeclaredFields()) { if ((f.getModifiers() & Modifier.STATIC) == 0) { fields.add(f); } } c = c.getSuperclass(); } // get offset long maxSize = 0; for (Field f : fields) { long offset = unsafe.objectFieldOffset(f); if (offset > maxSize) { maxSize = offset; } } return ((maxSize / 8) + 1) * 8; // padding }
大数组
-
java数组的最大大小是Integer.MAX_VALUE。使用直接内存分配,我们可以创建大小仅受堆大小限制的数组。
//java.lang.OutOfMemoryError: Requested array size exceeds VM limit int[] a = new int[Integer.MAX_VALUE]; //java.lang.OutOfMemoryError: Java heap space int[] b = new int[Integer.MAX_VALUE - 8]; 数组需要8个字节来存储大小,所以数组最大长度是Integer.MAX_VALUE - 8
-
实现大数组
public class SuperArray { public static final Unsafe UNSAFE = unsafe(); private final static int BYTE = 1; private long size; private long address; public SuperArray(long size) { this.size = size; //以这种方式分配的内存不在堆中,也不在GC的管理之下,因此需要使用Unsafe.freeMemory()进行处理。 //它还不执行任何边界检查,因此任何非法访问都可能导致JVM崩溃。 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; } private static Unsafe unsafe() { try { Field singleoneInstanceField = Unsafe.class.getDeclaredField("theUnsafe"); singleoneInstanceField.setAccessible(true); return (Unsafe) singleoneInstanceField.get(null); } catch (Exception e) { throw new RuntimeException(e.getMessage(), e); } } public void free() { UNSAFE.freeMemory(address); } } public class SuperArrayTest { @Test public void testSuperArray() { long SUPER_SIZE = (long) Integer.MAX_VALUE * 2; SuperArray array = new SuperArray(SUPER_SIZE); // 4294967294 System.out.println("Array size:" + array.size()); int sum = 0; for (int i = 0; i < 100; i++) { array.set((long) Integer.MAX_VALUE + i, (byte) 3); sum += array.get((long) Integer.MAX_VALUE + i); } // 300 System.out.println("100个元素的和:" + sum); //释放内存 array.free(); } }
多继承
-
在Java中本来是没有多继承的。除非我们可以将任意的类型转为我们想要的任意类型。除非我们可以转换任意类型到任意其他类型。
long intClassAddress = normalize(getUnsafe().getInt(new Integer(0),4L)); long strClassAddress = normalize(getUnsafe().getInt("",4L)); getUnsafe().putAddress(intClassAddress+36,strClassAddress);
这段代码添加
String
类成为Integer
的父类,所以,我们可以直接转换,而不会有运行时异常。(String)(Object)(new Integer(666))
动态class
-
我们可以在运行时创建类,例如读取编译好的 .class 文件。然后执行读 class 内容到 byte 数组,并且传递给
defineClass
方法。byte[] classContents = getClassContent(); Class c = getUnsafe().defineClass(null,classContents,0,classContents.length); c.getMethod("a").invoke(c.newInstance(),null);// 1 private static byte[] getClassContent() throws Exception{ File f = new File("/home/mishadoff/tmp/A.class"); FileInputStream input = new FileInputStream(f); byte[] content = new byte[(int)f.length()]; input.read(content); input.close(); return content; }