<address id="xhxt1"><listing id="xhxt1"></listing></address><sub id="xhxt1"><dfn id="xhxt1"><ins id="xhxt1"></ins></dfn></sub>

    <thead id="xhxt1"><dfn id="xhxt1"><ins id="xhxt1"></ins></dfn></thead>

    Java Magic. Part 4: sun.misc.Unsafe

    原文地址 译文地址 译者:许巧辉 校对:梁海舰

    Java是一门安全的编程语言,防止程序员犯很多愚蠢的错误,它们大部分是基于内存管理的。但是,有一种方式可以有意的执行一些不安全、容易犯错的操作,那就是使用Unsafe类。

    本文是sun.misc.Unsafe公共API的简要概述,及其一些有趣的用法。

    Unsafe 实例

    在使用Unsafe之前,我们需要创建Unsafe对象的实例。这并不像Unsafe unsafe = new Unsafe()这么简单,因为Unsafe的构造器是私有的。它也有一个静态的getUnsafe()方法,但如果你直接调用Unsafe.getUnsafe(),你可能会得到SecurityException异常。只能从受信任的代码中使用这个方法。

    public static Unsafe getUnsafe() {
        Class cc = sun.reflect.Reflection.getCallerClass(2);
        if (cc.getClassLoader() != null)
            throw new SecurityException("Unsafe");
        return theUnsafe;
    }
    

    这就是Java如何验证代码是否可信。它只检查我们的代码是否由主要的类加载器加载。

    我们可以令我们的代码“受信任”。运行程序时,使用bootclasspath 选项,指定系统类路径加上你使用的一个Unsafe路径。

    java -Xbootclasspath:/usr/jdk1.7.0/jre/lib/rt.jar:. com.mishadoff.magic.UnsafeClient
    

    但这太难了。

    Unsafe类包含一个私有的、名为theUnsafe的实例,我们可以通过Java反射窃取该变量。

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

    注意:忽略你的IDE。比如:eclipse显示”Access restriction…”错误,但如果你运行代码,它将正常运行。如果这个错误提示令人烦恼,可以通过以下设置来避免:

    Preferences -> Java -> Compiler -> Errors/Warnings ->
    Deprecated and restricted API -> Forbidden reference -> Warning
    

    Unsafe API

    sun.misc.Unsafe类包含105个方法。实际上,对各种实体操作有几组重要方法,其中的一些如下:

    Info.仅返回一些低级的内存信息

    • addressSize
    • pageSize

    Objects.提供用于操作对象及其字段的方法

    • allocateInstance
    • objectFieldOffset

    Classes.提供用于操作类及其静态字段的方法

    • staticFieldOffset
    • defineClass
    • defineAnonymousClass
    • ensureClassInitialized

    Arrays.操作数组

    • arrayBaseOffset
    • arrayIndexScale

    Synchronization.低级的同步原语

    • monitorEnter
    • tryMonitorEnter
    • monitorExit
    • compareAndSwapInt
    • putOrderedInt

    Memory.直接内存访问方法

    • allocateMemory
    • copyMemory
    • freeMemory
    • getAddress
    • getInt
    • putInt

    有趣的用例

    避免初始化

    当你想要跳过对象初始化阶段,或绕过构造器的安全检查,或实例化一个没有任何公共构造器的类,allocateInstance方法是非常有用的??悸且韵吕啵?/p>

    class A {
        private long a; // not initialized value
    
        public A() {
            this.a = 1; // initialization
        }
    
        public long a() { return this.a; }
    }
    

    使用构造器、反射和unsafe初始化它,将得到不同的结果。

    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
    

    想想所有单例发生了什么。

    内存崩溃(Memory corruption)

    这对于每个C程序员来说是常见的。顺便说一下,它是绕过安全的常用技术。

    考虑下那些用于检查“访问规则”的简单类:

    class Guard {
           private int ACCESS_ALLOWED = 1;
    
           public boolean giveAccess() {
                  return 42 == ACCESS_ALLOWED;
           }
    }
    

    客户端代码是非常安全的,并且通过调用giveAccess()来检查访问规则??上?,对于客户,它总是返回false。只有特权用户可以以某种方式改变ACCESS_ALLOWED常量的值并且得到访问(giveAccess()方法返回true,译者注)。

    实际上,这并不是真的。演示代码如下:

    Guard guard = new Guard();
    guard.giveAccess();   // false, no access
    
    // bypass
    Unsafe unsafe = getUnsafe();
    Field f = guard.getClass().getDeclaredField("ACCESS_ALLOWED");
    unsafe.putInt(guard, unsafe.objectFieldOffset(f), 42); // memory corruption
    
    guard.giveAccess(); // true, access granted
    

    现在所有的客户都拥有无限制的访问权限。

    实际上,反射可以实现相同的功能。但值得关注的是,我们可以修改任何对象,甚至没有这些对象的引用。

    例如,有一个guard对象,所在内存中的位置紧接着在当前guard对象之后。我们可以用以下代码来修改它的ACCESS_ALLOWED字段:

    unsafe.putInt(guard, 16 + unsafe.objectFieldOffset(f), 42); // memory corruption
    

    注意:我们不必持有这个对象的引用。16是Guard对象在32位架构上的大小。我们可以手工计算它,或者通过使用sizeOf方法(它的定义,如下节)。

    sizeOf

    使用objectFieldOffset方法可以实现C-风格(C-style)的sizeof方法。这个实现返回对象的自身内存大?。ㄒ胝咦ⅲ?a >shallow size)。

    public static long sizeOf(Object o) {
        Unsafe u = getUnsafe();
        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 = u.objectFieldOffset(f);
            if (offset > maxSize) {
                maxSize = offset;
            }
        }
    
        return ((maxSize/8) + 1) * 8;   // padding
    }
    

    算法如下:通过所有非静态字段(包含父类的),获取每个字段的偏移量(offset),找到偏移最大值并填充字节数(padding)。我可能错过一些东西,但思路是明确的。

    如果我们仅读取对象的类结构大小值,sizeOf的实现可以更简单,这位于JVM 1.7 32 bit中的偏移量12。

    public static long sizeOf(Object object){
        return getUnsafe().getAddress(
            normalize(getUnsafe().getInt(object, 4L)) + 12L);
    }
    

    normalize是一个为了正确内存地址使用,将有符号的int类型强制转换成无符号的long类型的方法。

    private static long normalize(int value) {
        if(value >= 0) return value;
        return (~0L >>> 32) & value;
    }
    

    真棒,这个方法返回的结果与我们之前的sizeof方法一样。

    实际上,对于良好、安全、准确的sizeof方法,最好使用?java.lang.instrument包,但这需要在JVM中指定agent选项。

    浅拷贝(Shallow copy)

    为了实现计算对象自身内存大小,我们可以简单地添加拷贝对象方法。标准的解决方案是使用Cloneable修改你的代码,或者在你的对象中实现自定义的拷贝方法,但它不会是多用途的方法。

    浅拷贝:

    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];
    }
    

    这个拷贝方法可以用来拷贝任何类型的对象,动态计算它的大小。注意,在拷贝后,你需要将对象转换成特定的类型。

    隐藏密码(Hide Password)

    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] = '?';
    }
    

    感谢Peter Verhas指定出这一点。

    多继承(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文件)中读?。ù耄┤缦拢?/p>

    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;
    }
    

    当你必须动态创建类,而现有代码中有一些代理, 这是很有用的。

    抛出异常(Throw an Exception)

    不喜欢受检异常?没问题。

    getUnsafe().throwException(new IOException());
    

    该方法抛出受检异常,但你的代码不必捕捉或重新抛出它,正如运行时异常一样。

    快速序列化(Fast Serialization)

    这更有实用性。

    大家都知道,标准Java的Serializable的序列化能力是非常慢的。它同时要求类必须有一个公共的、无参数的构造器。

    Externalizable比较好,但它需要定义类序列化的模式。

    流行的高性能库,比如kryo具有依赖性,这对于低内存要求来说是不可接受的。

    unsafe类可以很容易实现完整的序列化周期。

    序列化:

    • 使用反射构建模式对象,类只可做一次。
    • 使用Unsafe方法,如getLong、getInt、getObject等来检索实际字段值。
    • 添加类标识,以便有能力恢复该对象
    • 将它们写入文件或任意输出

    你也可以添加压缩(步骤)以节省空间。

    反序列化:

    • 创建已序列化对象实例,使用allocateInstance协助(即可),因为不需要任何构造器。
    • 构建模式,与序列化的步骤1相同。
    • 从文件或任意输入中读取所有字段。
    • 使用Unsafe方法,如putLong、putInt、putObject等来填充该对象。

    实际上,在正确的实现过程中还有更多的细节,但思路是明确的。

    这个序列化将非???。

    顺便说一下,在kryo中有使用Unsafe的一些尝试http://code.google.com/p/kryo/issues/detail?id=75

    大数组(Big Arrays

    正如你所知,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在大数组上延迟的限制。

    并发(Concurrency)

    几句关于Unsafe的并发性。compareAndSwap方法是原子的,并且可用来实现高性能的、无锁的数据结构。

    比如,考虑问题:在使用大量线程的共享对象上增长值。

    首先,我们定义简单的Counter接口:

    interface Counter {
        void increment();
        long getCounter();
    }
    

    然后,我们定义使用Counter的工作线程CounterClient

    class CounterClient implements Runnable {
        private Counter c;
        private int num;
    
        public CounterClient(Counter c, int num) {
            this.c = c;
            this.num = num;
        }
    
        @Override
        public void run() {
            for (int i = 0; i < num; i++) {
                c.increment();
            }
        }
    }
    

    测试代码:

    int NUM_OF_THREADS = 1000;
    int NUM_OF_INCREMENTS = 100000;
    ExecutorService service = Executors.newFixedThreadPool(NUM_OF_THREADS);
    Counter counter = ... // creating instance of specific counter
    long before = System.currentTimeMillis();
    for (int i = 0; i < NUM_OF_THREADS; i++) {
        service.submit(new CounterClient(counter, NUM_OF_INCREMENTS));
    }
    service.shutdown();
    service.awaitTermination(1, TimeUnit.MINUTES);
    long after = System.currentTimeMillis();
    System.out.println("Counter result: " + c.getCounter());
    System.out.println("Time passed in ms:" + (after - before));
    

    第一个无锁版本的计数器:

    class StupidCounter implements Counter {
        private long counter = 0;
    
        @Override
        public void increment() {
            counter++;
        }
    
        @Override
        public long getCounter() {
            return counter;
        }
    }
    

    输出:

    Counter result: 99542945
    Time passed in ms: 679
    

    运行快,但没有线程管理,结果是不准确的。第二次尝试,添加上最简单的java式同步:

    class SyncCounter implements Counter {
        private long counter = 0;
    
        @Override
        public synchronized void increment() {
            counter++;
        }
    
        @Override
        public long getCounter() {
            return counter;
        }
    }
    

    输出:

    
    Counter result: 100000000
    Time passed in ms: 10136
    

    激进的同步有效,但耗时长。试试ReentrantReadWriteLock

    class LockCounter implements Counter {
        private long counter = 0;
        private WriteLock lock = new ReentrantReadWriteLock().writeLock();
    
        @Override
        public void increment() {
            lock.lock();
            counter++;
            lock.unlock();
        }
    
        @Override
        public long getCounter() {
            return counter;
        }
    }
    

    输出:

    
    Counter result: 100000000
    Time passed in ms: 8065
    

    仍然正确,耗时较短。atomics的运行效果如何?

    class AtomicCounter implements Counter {
        AtomicLong counter = new AtomicLong(0);
    
        @Override
        public void increment() {
            counter.incrementAndGet();
        }
    
        @Override
        public long getCounter() {
            return counter.get();
        }
    }
    

    输出:

    
    Counter result: 100000000
    Time passed in ms: 6552
    

    AtomicCounter的运行结果更好。最后,试试Unsafe原始的compareAndSwapLong,看看它是否真的只有特权才能使用它?

    class CASCounter implements Counter {
        private volatile long counter = 0;
        private Unsafe unsafe;
        private long offset;
    
        public CASCounter() throws Exception {
            unsafe = getUnsafe();
            offset = unsafe.objectFieldOffset(CASCounter.class.getDeclaredField("counter"));
        }
    
        @Override
        public void increment() {
            long before = counter;
            while (!unsafe.compareAndSwapLong(this, offset, before, before + 1)) {
                before = counter;
            }
        }
    
        @Override
        public long getCounter() {
            return counter;
        }
    }
    

    输出:

    
    Counter result: 100000000
    Time passed in ms: 6454
    

    看起来似乎等价于atomics。atomics使用Unsafe?(是的)

    实际上,这个例子很简单,但它展示了Unsafe的一些能力。

    如我所说,CAS原语可以用来实现无锁的数据结构。背后的原理很简单:

    • 有一些状态
    • 创建它的副本
    • 修改它
    • 执行CAS
    • 如果失败,重复尝试

    实际上,现实中比你现象的更难。存在着许多问题,如ABA问题、指令重排序等。

    如果你真的感兴趣,可以参考lock-free HashMap的精彩展示。

    修改:给counter变量添加volatile关键字,以避免无限循环的风险。

    结论(Conclusion)

    即使Unsafe对应用程序很有用,但(建议)不要使用它。

    原创文章,转载请注明: 转载自并发编程网 – www.gofansmi6.com本文链接地址: Java Magic. Part 4: sun.misc.Unsafe


    FavoriteLoading添加本文到我的收藏
    • Trackback 关闭
    • 评论 (2)
      • zfox
      • 2014/02/28 4:39下午

      public class LockSupport01 {

      public static void main(String[] args) {
      Thread01 t =new Thread01();
      System.out.println(“unpark–start”);
      LockSupport.unpark(t); // ******************************1
      System.out.println(“unpark–end”);
      t.start();// ******************************2
      }
      }

      class Thread01 extends Thread {
      @Override
      public void run() {
      for(int i=0;i<10;i++){
      System.out.println(i);
      try {
      Thread.sleep(100);
      } catch (InterruptedException e) {
      e.printStackTrace();
      }
      }
      System.out.println("park—start");
      LockSupport.park();
      System.out.println("park—end");
      }
      }

      求解惑:
      如果代码是现在这样,线程会被park; 如果2放在1之上,线程就不会被park?
      Unsafe里面是怎么实现的?

        • yunchow
        • 2014/09/11 7:42下午

        你有点逗,你先unpauk,然后启动线程,在线程里又park,当然会wait了

    您必须 登陆 后才能发表评论

    return top

    爱投彩票 esm| m7k| aie| 7ma| uk7| ukm| uks| k6q| uus| 6gm| kw6| qom| c6o| ems| 6so| ay7| kow| ww5| ymu| muy| g5c| qgs| 5ma| oe5| igo| y6i| qgu| 6ym| ai4| sqw| m4i| ywu| emc| 4ei| eu5| eoc| y5e| ccs| 5go| ca5| qoq| k3g| okw| 3ws| iiw| ygy| 4iq| ww4| csa| e4q| wmk| 4kg| ee2| uiy| u3q| esg| 3gu| mm3| wc3| qgs|