哪個(gè)更快:Java堆還是本地內(nèi)存
使用Java的一個(gè)好處就是你可以不用親自來(lái)管理內(nèi)存的分配和釋放。當(dāng)你用new關(guān)鍵字來(lái)實(shí)例化一個(gè)對(duì)象時(shí),它所需的內(nèi)存會(huì)自動(dòng)的在Java堆中分配。堆會(huì)被垃圾回收器進(jìn)行管理,并且它會(huì)在對(duì)象超出作用域時(shí)進(jìn)行內(nèi)存回收。但是在JVM中有一個(gè)‘后門(mén)’可以讓你訪問(wèn)不在堆中的本地內(nèi)存(native   memory)。在這篇文章中,我會(huì)給你演示一個(gè)對(duì)象是怎樣以連續(xù)的字節(jié)碼的方式在內(nèi)存中進(jìn)行存儲(chǔ),并且告訴你是應(yīng)該怎樣存儲(chǔ)這些字節(jié),是在Java堆中還是在本地內(nèi)存中。最后我會(huì)就怎樣從JVM中訪問(wèn)內(nèi)存更快給一些結(jié)論:是用Java堆還是本地內(nèi)存。
使用Unsafe來(lái)分配和回收內(nèi)存
sun.misc.Unsafe可以讓你在Java中分配和回收本地內(nèi)存,就像C語(yǔ)言中的malloc和free。通過(guò)它分配的內(nèi)存不在Java堆中,并且不受垃圾回收器的管理,因此在它被使用完的時(shí)候你需要自己來(lái)負(fù)責(zé)釋放和回收。下面是我寫(xiě)的一個(gè)使用Unsafe來(lái)管理本地內(nèi)存的一個(gè)工具類(lèi):
- 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);
 - }
 - }
 
在本地內(nèi)存中分配一個(gè)對(duì)象
讓我們來(lái)將下面的Java對(duì)象放到本地內(nèi)存中:
- 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;
 - }
 - }
 
我們所做的僅僅是把對(duì)象的屬性放入到Memory中:
- 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);
 - }
 - }
 
現(xiàn)在我們來(lái)看看對(duì)兩個(gè)數(shù)組的讀寫(xiě)性能:其中一個(gè)含有數(shù)百萬(wàn)的SomeObject對(duì)象,另外一個(gè)含有數(shù)百萬(wàn)的SomeMemoryObject對(duì)象。
// 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
結(jié)論:跨越JVM的屏障來(lái)讀本地內(nèi)存大約會(huì)比直接讀Java堆中的內(nèi)存慢10倍,而對(duì)于寫(xiě)操作會(huì)慢大約2倍。但是需要注意的是,由于每一個(gè)SomeMemoryObject對(duì)象所管理的本地內(nèi)存空間都是獨(dú)立的,因此讀寫(xiě)操作都不是連續(xù)的。那么我們接下來(lái)就來(lái)對(duì)比下讀寫(xiě)連續(xù)的內(nèi)存空間的性能。
訪問(wèn)一大塊的連續(xù)內(nèi)存空間
這個(gè)測(cè)試分別在堆中和一大塊連續(xù)本地內(nèi)存中包含了相同的測(cè)試數(shù)據(jù)。然后我們來(lái)做多次的讀寫(xiě)操作看看哪個(gè)更快。并且我們會(huì)做一些隨機(jī)地址的訪問(wèn)來(lái)對(duì)比結(jié)果。
// 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
結(jié)論:在做連續(xù)訪問(wèn)的時(shí)候,Java堆內(nèi)存通常都比本地內(nèi)存要快。對(duì)于隨機(jī)地址訪問(wèn),堆內(nèi)存僅僅比本地內(nèi)存慢一點(diǎn)點(diǎn),并且是針對(duì)大塊連續(xù)數(shù)據(jù)的時(shí)候,而且沒(méi)有慢很多。
最后的結(jié)論
在Java中使用本地內(nèi)存有它的意義,比如當(dāng)你要操作大塊的數(shù)據(jù)時(shí)(>2G)并且不想使用垃圾回收器(GC)的時(shí)候。從延遲的角度來(lái)說(shuō),直接訪問(wèn)本地內(nèi)存不會(huì)比訪問(wèn)Java堆快。這個(gè)結(jié)論其實(shí)是有道理的,因?yàn)榭缭絁VM屏障肯定是有開(kāi)銷(xiāo)的。這樣的結(jié)論對(duì)使用本地還是堆的ByteBuffer同樣適用。使用本地ByteBuffer的速度提升不在于訪問(wèn)這些內(nèi)存,而是它可以直接與操作系統(tǒng)提供的本地IO進(jìn)行操作。















 
 
 











 
 
 
 