【翻譯】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.

相關文章
相關標籤/搜索