当前位置: 首页 > 科技观察

哪个更快:Java堆或本地内存

时间:2023-03-23 12:15:17 科技观察

使用Java的好处之一是您不必自己管理内存分配和释放。当你用new关键字实例化一个对象时,它需要的内存会自动分配到Java堆上。堆由垃圾收集器管理,当对象超出范围时它会回收内存。但是JVM中有一个“后门”,允许您访问不在堆中的本机内存。在本文中,我将向您展示对象如何作为连续字节码存储在内存中,并告诉您如何存储这些字节,无论是在Java堆中还是在本地内存中。.最后,我将给出一些关于如何更快地从JVM访问内存的结论:是使用Java堆还是本地内存。使用Unsafe分配和回收内存sun.misc.Unsafe允许你在Java中分配和回收原生内存,就像C中的malloc和free一样。通过它分配的内存不在Java堆中,不受垃圾收集器管理,因此您有责任在用完后自行释放和回收。下面是我写的一个使用Unsafe来管理本地存在的一个工具类:publicclassDirectimplementsMemory{privatestaticUnsafeunsafe;privatestaticbooleanAVAILABLE=false;static{try{Fieldfield=Unsafe.class.getDeclaredField("theUnsafe");field.setAccessible(true);un=(不安全)field.get(null);AVAILABLE=true;}catch(Exceptione){//NOOP:throwexceptionlaterwhenallocatingmemory}}publicstaticbooleanisAvailable(){returnAVAILABLE;}privatestaticDirectINSTANCE=null;publicstaticMemorygetInstance(){if(INSTANCE==null){INSTANCE=newDirect();}returnINSTANCE;}privateDirect(){}@Overridepubliclongalloc(longsize){if(!AVAILABLE){thrownewIllegalStateException("sun.misc.Unsafeisnotaccessible!");}returnunsafe.allocateMemory(size);}@Overridepublicvoidfree(longaddress){unsafe.freeMemory(address);}@OverridepublicfinallonggetLong(longaddress){returnunsafe.getLong(address);}@OverridepublicfinalvoidputLong(longaddress,longvalue){unsafe.putLong(address,value);}@OverridepublicfinalintgetInt(longaddress){returnunsafe.getInt(address);}@OverridepublicfinalvoidputInt(longaddress,intvalue){unsafe.putInt(address,value);}}在本地内存中分配一个对象让我们将以下Java对象放入本地内存中:publicclassSomeObject{privatelongsomeLong;privateintsomeInt;publiclonggetSomeLong(){returnsomeLong;}publicvoidsetSomeLong(longsomeLong){this.对象的属性放入Memory:publicclassSomeMemoryObject{privatefinalstaticintsomeLong_OFFSET=0;privatefinalstaticintsomeInt_OFFSET=8;privatefinalstaticintSIZE=8+4;//onelong+oneintprivatelongaddress;privatefinalMemorymemory;publicSomeMemoryObject(Memorymemory.this=memory.addres){this=memory.addresalloc(SIZE);}@Overridepublicvoidfinalize(){memory.free(address);}publicfinalvoidsetSomeLong(longsomeLong){memory.putLong(address+someLong_OFFSET,someLong);}publicfinallonggetSomeLong(){returnmemory.getLong(address+someLong}_OFFSET);publicfinalvoidsetSomeInt(intsomeInt){memory.putInt(address+someInt_OFFSET,someInt);}publicfinalintgetSomeInt(){returnmemory.getInt(address+someInt_OFFSET);}}下面看两个数组的读写性能:其中一个数组包含数百万个SomeObject对象,另外一含有数百万的的的//withjit:对象数:1,0001,000,00010,000,000,000,000,000,000,000HeapAVG写作:1072.302.302.512.512.58本机avg写入:3056.656.656.656.655.945.945.26heap读取:610.310.280.280.280.28NITEBALAVG请阅读:3093.502.962.16//没有JIT:(-XINT)对象数:1,0001,000,00010,000,000,000,000,000,000HeapAVG写作:104107105105102本地AVG写作:292293300297HEAPAVG阅读:59636058nativeavg读:296636058nativativateavg298302299结论:跨JVM屏障读取本地内存比直接读取Java堆内存慢10倍左右,写操作慢2倍左右。但是需要注意的是,由于每个SomeMemoryObject对象所管理的本地内存空间是独立的,所以读写操作并不是连续的。那么我们来比较读写连续内存空间的性能。访问大的连续内存空间该测试在堆和大的连续本地内存中包含相同的测试数据。然后我们做多次读写操作,看哪个更快。我们会做一些随机地址访问来比较结果。//使用JIT和顺序访问:对象数量:1,0001,000,0001,000,000,000HeapAvgWrite:120.340.35NativeAvgWrite:1020.710.69HeapAvgRead:120.290.28NativeAvgRead:1100.320.32无JIT和顺序访问//:(-Xint)对象数量:1,0001,000,00010,000,000HeapAvgWrite:888NativeAvgWrite:919294HeapAvgRead:101010NativeAvgRead:919094//使用JIT和0随机访问对象:Number1,1,000,0001,000,000,000HeapAVG写作:611.011.12统一的AVG写作:1510.890.90堆AVG阅读:590.890.920.92本机AVG阅读:1560.780.840.84//无jit和随机访问AvgWrite:555555NativeAvgWrite:141142140HeapAvgRead:555555NativeAvgRead:138140138结论:在进行连续访问时,Java堆内存通常比本地内存快。对于随机地址访问,堆内存只比本地内存慢一点点,对于大的连续数据,也慢不了多少。最终结论在Java中使用本机内存有它的意义,例如,当您要对大块数据(>2G)进行操作并且不想使用垃圾收集器(GC)时。从延迟的角度来看,直接访问本机内存并不比访问Java堆快。这个结论其实是有道理的,因为越过JVMbarrier肯定是有开销的。这个结论同样适用于使用local或者heap的ByteBuffer。使用本地ByteBuffer的速度提升不是去访问这些内存,而是可以直接用操作系统提供的本地IO进行操作。

最新推荐
猜你喜欢