Java中大部分错误都是基于内存管理方面的。如果想破坏,可以使用Unsafe这个类。
实例化Unsafe:
下面两种方式是不行的
private Unsafe() {} //私有构造方法 @CallerSensitive public static Unsafe getUnsafe() { Class var0 = Reflection.getCallerClass(); if(!VM.isSystemDomainLoader(var0.getClassLoader())) {//如果不是JDK信任的类去实例化,则抛出异常 throw new SecurityException("Unsafe"); } else { return theUnsafe; } }
所以,简单方式就是通过反射去实例化Unsafe
Field f = Unsafe.class.getDeclaredField("theUnsafe"); //Internal reference f.setAccessible(true); Unsafe unsafe = (Unsafe) f.get(null);
避免初始化
当你想要跳过对象初始化阶段,或绕过构造器的安全检查,或实例化一个没有任何公共构造器的类,allocateInstance
方法是非常有用的。
import sun.misc.Unsafe; import java.lang.reflect.Field; public class App { public static void main( String[] args ) throws Exception { Field f = Unsafe.class.getDeclaredField("theUnsafe"); //Internal reference f.setAccessible(true); Unsafe unsafe = (Unsafe) f.get(null); A o1 = new A(); // constructor o1.a(); // prints 1 A o2 = A.class.newInstance(); // reflection o2.a(); // prints 1 A o3 = (A) unsafe.allocateInstance(A.class); // unsafe o3.a(); // prints 0 } } class A { private long a; // not initialized value public A() { this.a = 1; // initialization } public void a() { System.out.println(this.a); } }
使用直接获取内存的方式实现浅克隆
如何实现浅克隆?在clone(){...}方法中调用super.clone(),对吗?这里存在的问题是首先你必须继续Cloneable接口,并且在所有你需要做浅克隆的对象中实现clone()方法,对于一个懒懒的程序员来说,这个工作量太大了。
浅克隆
static Object shallowCopy(Object obj) { long size = sizeOf(obj); long start = toAddress(obj); long address = getUnsafe().allocateMemory(size); getUnsafe().copyMemory(start, address, size); return fromAddress(address); }
toAddress和
fromAddress
将对象转换为其在内存中的地址,反之亦然。
static long toAddress(Object obj) { Object[] array = new Object[] {obj}; long baseOffset = getUnsafe().arrayBaseOffset(Object[].class); return normalize(getUnsafe().getInt(array, baseOffset)); } static Object fromAddress(long address) { Object[] array = new Object[] {null}; long baseOffset = getUnsafe().arrayBaseOffset(Object[].class); getUnsafe().putLong(array, baseOffset, address); return array[0]; }
这个拷贝方法可以用来拷贝任何类型的对象,动态计算它的大小。注意,在拷贝后,你需要将对象转换成特定的类型。
隐藏密码
在Unsafe
中,一个更有趣的直接内存访问的用法是,从内存中删除不必要的对象。
检索用户密码的大多数API的签名为byte[]
或char[],
为什么是数组呢?
这完全是出于安全的考虑,因为我们可以删除不需要的数组元素。如果将用户密码检索成字符串,这可以像一个对象一样在内存中保存,而删除该对象只需执行解除引用的操作。但是,这个对象仍然在内存中,由GC决定的时间来执行清除。
创建具有相同大小、假的String对象,来取代在内存中原来的String对象的技巧:
String password = new String("[email protected]$e"); String fake = new String(password.replaceAll(".", "?")); System.out.println(password); // [email protected]$e System.out.println(fake); // ???????????? getUnsafe().copyMemory(fake, 0L, null, toAddress(password), sizeOf(password)); System.out.println(password); // ???????????? System.out.println(fake); // ????????????
我们需要通过反射删除后台char数组:
Field stringValue = String.class.getDeclaredField("value"); stringValue.setAccessible(true); char[] mem = (char[]) stringValue.get(password); for (int i=0; i < mem.length; i++) { mem[i] = ‘?‘; }
多继承(Multiple Inheritance)
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))
有一个问题,我们必须预先强制转换对象,以欺骗编译器。
动态类(Dynamic classes)
我们可以在运行时创建一个类,比如从已编译的.class文件中。将类内容读取为字节数组,并正确地传递给defineClass
方法。
byte[] classContents = getClassContent(); Class c = getUnsafe().defineClass( null, classContents, 0, classContents.length); c.getMethod("a").invoke(c.newInstance(), null); // 1
从定义文件(class文件)中读取(代码)如下:
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; }
大数组
正如你所知,Java数组大小的最大值为Integer.MAX_VALUE
。使用直接内存分配,我们创建的数组大小受限于堆大小。
SuperArray的实现
:
class SuperArray { private final static int BYTE = 1; private long size; private long address; public SuperArray(long size) { this.size = size; address = getUnsafe().allocateMemory(size * BYTE); } public void set(long i, byte value) { getUnsafe().putByte(address + i * BYTE, value); } public int get(long idx) { return getUnsafe().getByte(address + idx * BYTE); } public long size() { return size; } }
用法:
long SUPER_SIZE = (long)Integer.MAX_VALUE * 2; SuperArray array = new SuperArray(SUPER_SIZE); System.out.println("Array size:" + array.size()); // 4294967294 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); // 300
实际上,这是堆外内存(off-heap memory
)技术,在java.nio
包中部分可用。
这种方式的内存分配不在堆上,且不受GC管理,所以必须小心Unsafe.freeMemory()的使用。它也不执行任何边界检查,所以任何非法访问可能会导致JVM崩溃。
这可用于数学计算,代码可操作大数组的数据。此外,这可引起实时程序员的兴趣,可打破GC在大数组上延迟的限制。
结论(Conclusion)
即使Unsafe
对应用程序很有用,但(建议)不要使用它。
原创文章,转载请注明: 转载自并发编程网 – ifeve.com本文链接地址: Java Magic. Part 4: sun.misc.Unsafe