【翻译】JAVA堆和原生内存谁更快?

这是个人第一篇翻译的比较完整的博文,如有错处请指出。这篇文章从两个测试去比较了JAVA堆和原生内存的读写操做。
译文出处:http://lipspace.duapp.com

原文出处:http://mentablog.soliveirajr.com/2012/11/which-one-is-faster-java-heap-or-native-memory/ php


JAVA语言有一个优势就是你不须要去处理内存的分配和释放。当你使用new关键字去实例化一个对象的时候,必要的内存就会分配到JVM堆里面。这个堆是由垃圾回收器管理着,当这个对象没有被引用的时候它的内存就会被回收掉。可是这里仍是有一个后门能够直接经过JVM访问原生内存。在这篇文章里我将会展现一下如何把一个对象做为一个字节序列存储在内存里,而且讲讲你该选择堆仍是直接内存(即原生内存)去保存这些字节。最后我会总结一下在JVM里面去读取堆内存仍是直接内存更快。 html

使用Unsafe去分配和释放

sun.misc.Unsafe 类容许你像在C里面调用malloc 和free 那样在JAVA里面去分配和释放原生内存。你建立的内存将会脱离堆而且再也不受到垃圾回收器的管理,也就是说这部份内存在使用完以后的释放就是你的责任了。下面个人Direct类展现如何使用unsafe类。 java

public class Direct implements Memory {

        private static Unsafe unsafe;
        private static boolean AVAILABLE = false;

        static {
                try {
                        Field field = Unsafe.class.getDeclaredField("theUnsafe");
                        field.setAccessible(true);
                        unsafe = (Unsafe)field.get(null);
                        AVAILABLE = true;
                } catch(Exception e) {
                        // NOOP: throw exception later when allocating memory
                }
    }

        public static boolean isAvailable() {
                return AVAILABLE;
        }

        private static Direct INSTANCE = null;

        public static Memory getInstance() {
                if (INSTANCE == null) {
                        INSTANCE = new Direct();
                }
                return INSTANCE;
        }

        private Direct() {

        }

        @Override
        public long alloc(long size) {
                if (!AVAILABLE) {
                        throw new IllegalStateException("sun.misc.Unsafe is not accessible!");
                }
                return unsafe.allocateMemory(size);
        }

        @Override
        public void free(long address) {
                unsafe.freeMemory(address);
        }

        @Override
        public final long getLong(long address) {
                return unsafe.getLong(address);
        }

        @Override
        public final void putLong(long address, long value) {
                unsafe.putLong(address, value);
        }

        @Override
        public final int getInt(long address) {
                return unsafe.getInt(address);
        }

        @Override
        public final void putInt(long address, int value) {
                unsafe.putInt(address, value);
        }

        @Override
        public final void putByte(long address, byte value) {
                unsafe.putByte(address, value);
        }

        @Override
        public final byte getByte(long address) {
                return unsafe.getByte(address);
        }
}

把一个对象存储在原生内存

等一下咱们就会将下面的一个JAVA对象存储到原生内存里面: web

public class SomeObject {

    private long someLong;
    private int someInt;

    public long getSomeLong() {
        return someLong;
    }
    public void setSomeLong(long someLong) {
        this.someLong = someLong;
    }
    public int getSomeInt() {
        return someInt;
    }
    public void setSomeInt(int someInt) {
        this.someInt = someInt;
    }
}

注意咱们下面作的是为了可以在 内存 里面存储它的属性: 数组

public class SomeMemoryObject {

    private final static int someLong_OFFSET = 0;
    private final static int someInt_OFFSET = 8;
    private final static int SIZE = 8 + 4; // one long + one int

    private long address;
    private final Memory memory;

    public SomeMemoryObject(Memory memory) {
        this.memory = memory;
        this.address = memory.alloc(SIZE);
    }

    @Override
    public void finalize() {
        memory.free(address);
    }

    public final void setSomeLong(long someLong) {
        memory.putLong(address + someLong_OFFSET, someLong);
    }

    public final long getSomeLong() {
        return memory.getLong(address + someLong_OFFSET);
    }

    public final void setSomeInt(int someInt) {
        memory.putInt(address + someInt_OFFSET, someInt);
    }

    public final int getSomeInt() {
        return memory.getInt(address + someInt_OFFSET);
    }
}

如今咱们来为两个数组进行标准的读写访问操做:一个数组有成千上百万个SomeObjects ,另外一个有成千上百万个SomeMemoryObjects。完整的代码能够在这里看到,输出结果以下: app

// with JIT:
Number of Objects:  1,000     1,000,000     10,000,000    60,000,000
Heap Avg Write:      107         2.30          2.51         2.58       
Native Avg Write:    305         6.65          5.94         5.26
Heap Avg Read:       61          0.31          0.28         0.28
Native Avg Read:     309         3.50          2.96         2.16
// without JIT: (-Xint)
Number of Objects:  1,000     1,000,000     10,000,000    60,000,000
Heap Avg Write:      104         107           105         102       
Native Avg Write:    292         293           300         297
Heap Avg Read:       59          63            60          58
Native Avg Read:     297         298           302         299

总结一下:经过JVM去直接访问原生内存读取大约要慢上十倍,写入的话大约要慢两倍。可是注意每个SomeMemoryObject都是分配了它本身的原生内存空间,因此读取和写入都是不连续的,也就是说,每个直接内存对象读写的来源和去往它本身的分配内存空间都是有可能在任何地方的。接下来咱们继续利用标准的读写访问操做去肯定一下连续的直接内存和堆内存哪一个更快。 dom

访问连续的大块内存

这个测试包括了在堆内存里面分配一个byte数组和在原生内存里面对应分配一个chunk,二者存储的数据量是相同的。而后咱们连续的写入和读取几回来看看哪个更快。同时咱们也测试了随机访问数组里面任何一个位置去比较一下结果。连续块测试代码在这里,随机测试代码在这里。结果以下: ide

// with JIT and sequential access:
Number of Objects:  1,000     1,000,000     1,000,000,000
Heap Avg Write:      12          0.34           0.35 
Native Avg Write:    102         0.71           0.69 
Heap Avg Read:       12          0.29           0.28 
Native Avg Read:     110         0.32           0.32
// without JIT and sequential access: (-Xint)
Number of Objects:  1,000     1,000,000      10,000,000
Heap Avg Write:      8           8              8
Native Avg Write:    91          92             94
Heap Avg Read:       10          10             10
Native Avg Read:     91          90             94
// with JIT and random access:
Number of Objects:  1,000     1,000,000     1,000,000,000
Heap Avg Write:      61          1.01           1.12
Native Avg Write:    151         0.89           0.90 
Heap Avg Read:       59          0.89           0.92 
Native Avg Read:     156         0.78           0.84
// without JIT and random access: (-Xint)
Number of Objects:  1,000     1,000,000      10,000,000
Heap Avg Write:      55          55              55
Native Avg Write:    141         142             140
Heap Avg Read:       55          55              55 
Native Avg Read:     138         140             138

结论:在连续块访问里,堆内存老是比直接内存要快。在随机访问里,堆内存比大的chunks存储要慢上一点,不是不少。 svn

最后结论

在JAVA里面使用原生内存有着必定的用途,例如当你想要操做大量的数据(>2 gigabytes)或者当你想脱离垃圾回收机制的时候。然而,在上面作的测试能够看到,经过JVM里面访问直接内存是不比访问堆内存快的。这个结果是有道理的,由于在穿过JVM的时候是有必定的消耗的,不管是在使用堆的ByteBuffer仍是使用直接内存的都是有相同的问题。而直接内存 ByteBuffer 的速度优点并非指访问的速度优点,而是指直接和操做系统原生I/O操做的直接会话能力优点。另外一个不错的讨论在 Peter Lawrey 的博客里面是关于随着工做时间序列的推移内存映射文件的使用( the use of memory-mapped files when working with time-series.),你们能够看看。 测试

[1] 更多避免 GC 的方法能够看做者以前的一篇文章 Real-time programming without the GC.

相关文章
相关标签/搜索