Java を使用する利点の 1 つは、メモリ割り当てと解放を自分で管理する必要がないことです。 new
キーワードを使用してオブジェクトをインスタンス化すると、オブジェクトに必要なメモリが Java ヒープに自動的に割り当てられます。ヒープはガベージ コレクターによって管理され、オブジェクトがスコープ外になるとメモリが再利用されます。ただし、JVM には、ヒープ内にないネイティブ メモリにアクセスできる「バックドア」があります。この記事では、オブジェクトが連続バイトコードとしてメモリにどのように格納されるかを示し、これらのバイトが Java ヒープまたはローカル メモリにどのように格納されるべきかを説明します。最後に、Java ヒープまたはローカル メモリを使用して、JVM からメモリに高速にアクセスする方法について結論をいくつか示します。 new
关键字来实例化一个对象时,它所需的内存会自动的在Java堆中分配。堆会被垃圾回收器进行管理,并且它会在对象超出作用域时进行内存回收。但是在JVM中有一个‘后门’可以让你访问不在堆中的本地内存(native memory)。在这篇文章中,我会给你演示一个对象是怎样以连续的字节码的方式在内存中进行存储,并且告诉你是应该怎样存储这些字节,是在Java堆中还是在本地内存中。最后我会就怎样从JVM中访问内存更快给一些结论:是用Java堆还是本地内存。
Unsafe
来分配和回收内存sun.misc.Unsafe
可以让你在Java中分配和回收本地内存,就像C语言中的malloc
和free
。通过它分配的内存不在Java堆中,并且不受垃圾回收器的管理,因此在它被使用完的时候你需要自己来负责释放和回收。下面是我写的一个使用Unsafe
来管理本地内存的一个工具类:
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); } }
让我们来将下面的Java对象放到本地内存中:
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; } }
我们所做的仅仅是把对象的属性放入到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); } }
现在我们来看看对两个数组的读写性能:其中一个含有数百万的SomeObject
对象,另外一个含有数百万的SomeMemoryObject
对象。
// 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的屏障来读本地内存大约会比直接读Java堆中的内存慢10倍,而对于写操作会慢大约2倍。但是需要注意的是,由于每一个SomeMemoryObject对象所管理的本地内存空间都是独立的,因此读写操作都不是连续的。那么我们接下来就来对比下读写连续的内存空间的性能。
这个测试分别在堆中和一大块连续本地内存中包含了相同的测试数据。然后我们来做多次的读写操作看看哪个更快。并且我们会做一些随机地址的访问来对比结果。
// 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
结论:在做连续访问的时候,Java堆内存通常都比本地内存要快。对于随机地址访问,堆内存仅仅比本地内存慢一点点,并且是针对大块连续数据的时候,而且没有慢很多。
在Java中使用本地内存有它的意义,比如当你要操作大块的数据时(>2G)并且不想使用垃圾回收器(GC)的时候。从延迟的角度来说,直接访问本地内存不会比访问Java堆快。这个结论其实是有道理的,因为跨越JVM屏障肯定是有开销的。这样的结论对使用本地还是堆的ByteBuffer
Unsafe
を使用しますsun.misc.Unsafe
を使用すると、C malloc と free
です。これによって割り当てられたメモリは Java ヒープにはなく、ガベージ コレクターによって管理されないため、使い切った場合は自分で解放してリサイクルする必要があります。以下は、Unsafe
を使用してローカル メモリを管理する私が作成したツール クラスです: 🎜rrreeeメモリに入れることです。 code>: 🎜rrreee🎜 次に、2 つの <a href="http://www.php.cn/wiki/58.html" target="_blank"> 配列の読み取りおよび書き込みパフォーマンス</a>) を見てみましょう。 >: 1 つは数百万の <code>SomeObject
オブジェクトを含み、もう 1 つは数百万の SomeMemoryObject
オブジェクトを含みます。 🎜rrreee🎜結論: JVM バリアを越えたローカル メモリの読み取りは、Java ヒープ内のメモリを直接読み取るよりも約 10 倍遅く、書き込み操作は約 2 倍遅くなります。ただし、各 SomeMemoryObject オブジェクトが管理するローカル メモリ空間は、独立しているため、読み取りと書き込みの操作は連続しません。次に、連続メモリ空間の読み取りおよび書き込みのパフォーマンスを比較してみましょう。 🎜ByteBuffer
が使用されるかにも当てはまります。ローカル ByteBuffer を使用することで速度が向上するのは、これらのメモリにアクセスすることではなく、オペレーティング システムが提供するローカル IO で直接動作できることです🎜以上がJava ヒープとローカル メモリではどちらが高速ですか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。