zoukankan      html  css  js  c++  java
  • sun.misc.unsafe

    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("l00k@myHor$e");
    String fake = new String(password.replaceAll(".", "?"));
    System.out.println(password); // l00k@myHor$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

  • 相关阅读:
    AOP面向切面编程基础
    记第一次年会主持
    Tomcat服务器部署JavaWeb项目War包基本步骤
    VM14无法将网络更改为桥接状态:没有未桥接的主机网络适配器
    Ubuntu 16.04 安装 IDEA
    Linux之文件挂载
    图片大小
    打开文件、
    Setup Factory
    Repeater获取某一行TextBox值
  • 原文地址:https://www.cnblogs.com/gudulijia/p/6685514.html
Copyright © 2011-2022 走看看