Java:ChronicleMap第1部分,精简版
用數(shù)百萬個對象填充HashMap會很快導(dǎo)致諸如內(nèi)存使用效率低下,性能低下和垃圾回收等問題。 了解如何使用堆外CronicleMap ,其中可以包含數(shù)十億個對象,而對堆的影響很小或沒有。
當我們要使用中小型數(shù)據(jù)集時,內(nèi)置的Map實現(xiàn)(例如HashMap和ConcurrentHashMap是出色的工具。 但是,隨著數(shù)據(jù)量的增長,這些
Map的實現(xiàn)正在惡化,并開始表現(xiàn)出如約開放sourceed的系列文章的第一篇文章中一些令人不快的缺點CronicleMap 。
堆分配
在下面的示例中,我們將使用Point對象。 Point是一個POJO,具有公共默認構(gòu)造函數(shù)以及X和Y屬性(int)的getter和setter。 以下代碼段將一百萬個Point對象添加到HashMap :
final Map<Long, Point> m = LongStream.range( 0 , 1_000_000) .boxed() .collect( toMap( Function.identity(), FillMaps::pointFrom, (u,v) -> { throw new IllegalStateException(); }, IllegalStateException(); }, HashMap:: new ) ); // Conveniency method that creates a Point from // a long by applying modulo prime number operations private static Point pointFrom( long seed) { final Point point = new Point(); point.setX(( int ) seed % 4517 ); point.setY(( int ) seed % 5011 ); return point; }我們可以很容易地看到堆上分配的對象數(shù)量以及這些對象消耗多少堆內(nèi)存:
Pers-MacBook-Pro:chronicle-test pemi$ jmap -histo 34366 | head | head num #instances #bytes class name (module) ------------------------------------------------------- 1 : 1002429 32077728 java.util.HashMap$Node (java.base @10 ) 2 : 1000128 24003072 java.lang.Long (java.base @10 ) 3 : 1000000 24000000 com.speedment.chronicle.test.map.Point 4 : 454 8434256 [Ljava.util.HashMap$Node; (java.base [Ljava.util.HashMap$Node; (java.base @10 ) 5 : 3427 870104 [B (java.base @10 ) 6 : 185 746312 [I (java.base @10 ) 7 : 839 102696 java.lang.Class (java.base @10 ) 8 : 1164 89088 [Ljava.lang.Object; (java.base [Ljava.lang.Object; (java.base @10 )對于每個Map條目,都需要在堆上創(chuàng)建Long , HashMap$Node和Point對象。 還有許多創(chuàng)建了HashMap$Node對象的數(shù)組。 這些對象和數(shù)組總共消耗了88,515,056字節(jié)的堆內(nèi)存。 因此,每個條目平均消耗88.5個字節(jié)。
注意:額外的2429 HashMap$Node對象來自Java內(nèi)部使用的其他HashMap對象。
堆外分配
與此相反, CronicleMap使用很少的堆內(nèi)存,運行以下代碼時可以看到:
final Map<Long, Point> m2 = LongStream.range( 0 , 1_000_000) .boxed() .collect( toMap( Function.identity(), FillMaps::pointFrom, (u,v) -> { throw new IllegalStateException(); }, IllegalStateException(); }, () -> ChronicleMap .of(Long. class , Point. class ) .averageValueSize( 8 ) .valueMarshaller(PointSerializer.getInstance()) .entries(1_000_000) .create() ) ); Pers-MacBook-Pro:chronicle-test pemi$ jmap -histo 34413 | head | head num #instances #bytes class name (module) ------------------------------------------------------- 1 : 6537 1017768 [B (java.base @10 ) 2 : 448 563936 [I (java.base @10 ) 3 : 1899 227480 java.lang.Class (java.base @10 ) 4 : 6294 151056 java.lang.String (java.base @10 ) 5 : 2456 145992 [Ljava.lang.Object; (java.base [Ljava.lang.Object; (java.base @10 ) 6 : 3351 107232 java.util.concurrent.ConcurrentHashMap$Node (java.base @10 ) 7 : 2537 81184 java.util.HashMap$Node (java.base @10 ) 8 : 512 49360 [Ljava.util.HashMap$Node; (java.base [Ljava.util.HashMap$Node; (java.base @10 ) 可以看出,沒有為Java分配對象
CronicleMap條目,因此也沒有堆內(nèi)存。
CronicleMap不會分配堆內(nèi)存,而是分配堆外內(nèi)存,而不是分配堆內(nèi)存。 假設(shè)我們使用標志-XX:NativeMemoryTracking=summary啟動JVM,則可以通過發(fā)出以下命令來檢索正在使用的堆外內(nèi)存量:
Pers-MacBook-Pro:chronicle-test pemi$ jcmd 34413 VM.native_memory | grep Internal VM.native_memory | grep Internal - Internal (reserved=30229KB, committed=30229KB) 顯然,我們的一百萬個對象使用略多于30 MB的堆外RAM放置在堆外內(nèi)存中。 這意味著,
CronicleMap使用的CronicleMap平均需要30個字節(jié)。
這比需要88.5字節(jié)的HashMap內(nèi)存有效得多。 實際上,我們節(jié)省了66%的RAM內(nèi)存和近100%的堆內(nèi)存。 后者很重要,因為Java垃圾收集器只能看到堆上的對象。
注意,我們必須在創(chuàng)建時決定CronicleMap最多可以容納多少個條目。 相比于
HashMap可以隨著我們添加新的關(guān)聯(lián)而動態(tài)增長。 我們還必須提供一個序列化程序 (即PointSerializer.getInstance() ),本文稍后將對此進行詳細討論。
垃圾收集
許多垃圾回收(GC)算法在與堆中存在的對象的平方成比例的時間內(nèi)完成。 因此,例如,如果我們將堆上的對象數(shù)量加倍,則可以預(yù)期GC將花費四倍的時間才能完成。
另一方面,如果我們創(chuàng)建的對象增加了64倍,則預(yù)期的預(yù)期GC時間將增加1,024倍。 這有效地阻止了我們創(chuàng)造出巨大的
HashMap對象。
使用ChronicleMap我們可以放置新的關(guān)聯(lián),而無需擔(dān)心垃圾收集時間。
序列化器
堆內(nèi)存與堆外內(nèi)存之間的介體通常稱為
序列化器 。 ChronicleMap帶有許多針對大多數(shù)內(nèi)置Java類型(例如Integer , Long , String等)的預(yù)配置序列化器。
在上面的示例中,我們使用了一個自定義的序列化程序,該序列化程序用于在堆內(nèi)存與堆外內(nèi)存之間來回轉(zhuǎn)換Point 。 序列化器類如下所示:
public final class PointSerializer implements SizedReader<Point>, SizedWriter<Point> { private static PointSerializer INSTANCE = new PointSerializer(); public static PointSerializer getInstance() { return INSTANCE; } INSTANCE; } private PointSerializer() {} @Override public long size( @NotNull Point toWrite) { return Integer.BYTES * 2 ; } @Override public void write(Bytes out, long size, @NotNull Point point) { out.writeInt(point.getX()); out.writeInt(point.getY()); } @NotNull @Override public Point read(Bytes in, long size, Point using) { @Nullable Point using) { if (using == null ) { using = new Point(); } using.setX(in.readInt()); using.setY(in.readInt()); return using; } } 上面的序列化器實現(xiàn)為無狀態(tài)單例,并且write()和read()方法中的實際序列化非常簡單。 唯一棘手的部分是,我們需要在
如果“ using”變量未引用實例化/重用的對象,則為read()方法。
如何安裝?
當我們想在項目中使用ChronicleMap時,只需在pom.xml文件中添加以下Maven依賴項,就可以訪問該庫。
< dependency > < groupId >net.openhft</ groupId > < artifactId >chronicle-map</ artifactId > < version >3.17.3</ version > </ dependency >如果您使用的是另一種構(gòu)建工具(例如Gradle),則可以通過單擊此鏈接來了解如何依賴ChronicleMap 。
短篇小說
以下是ChronicleMap的一些屬性:
堆外存儲數(shù)據(jù)
幾乎總是比HashMap更高的內(nèi)存效率
實現(xiàn)ConcurrentMap 不影響垃圾收集時間 有時需要一個序列化器 具有固定的最大條目大小 可以容納數(shù)十億個協(xié)會 是免費和開源的
翻譯自: https://www.javacodegeeks.com/2019/08/java-chroniclemap-part-1-go-off-heap.html
總結(jié)
以上是生活随笔為你收集整理的Java:ChronicleMap第1部分,精简版的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 怎么看路由器被ddos攻击了(怎么看路由
- 下一篇: 三国战纪安卓版(三国战纪安卓)