精打细算油盐_Java:ChronicleMap第1部分,精打细算
精打細算油鹽
用數百萬個對象填充HashMap會很快導致諸如內存使用效率低下,性能低下和垃圾回收等問題。 了解如何使用堆外CronicleMap ,其中可以包含數十億個對象,而對堆的影響很小或沒有。
當我們要使用中小型數據集時,內置的Map實現(例如HashMap和ConcurrentHashMap是出色的工具。 但是,隨著數據量的增長,這些
正如開放源代碼CronicleMap系列文章中的第一篇文章所述, Map實現正在惡化并開始表現出許多令人不快的缺點。
堆分配
在下面的示例中,我們將使用Point對象。 Point是一個POJO,具有公共默認構造函數以及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; }我們可以很容易地看到堆上分配的對象數量以及這些對象消耗多少堆內存:
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條目,都需要在堆上創建Long , HashMap$Node和Point對象。 還有許多創建了HashMap$Node對象的數組。 這些對象和數組總共消耗了88,515,056字節的堆內存。 因此,每個條目平均消耗88.5個字節。
注意:額外的2429 HashMap$Node對象來自Java內部使用的其他HashMap對象。
堆外分配
與此相反,如運行以下代碼所示, CronicleMap使用的堆內存很少:
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條目,因此也沒有堆內存。
CronicleMap不用分配堆內存, CronicleMap分配其堆外內存。 假設我們使用標志-XX:NativeMemoryTracking=summary啟動JVM,則可以通過發出以下命令來檢索正在使用的堆外內存量:
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放置在堆外內存中。 這意味著,
CronicleMap使用的CronicleMap平均需要30個字節。
這比需要88.5字節的HashMap內存有效得多。 實際上,我們節省了66%的RAM內存和近100%的堆內存。 后者很重要,因為Java垃圾收集器只能看到堆上的對象。
請注意,我們必須在創建時決定CronicleMap最多可以容納多少個條目。 相比于
HashMap可以隨著我們添加新的關聯而動態增長。 我們還必須提供一個序列化程序 (即PointSerializer.getInstance() ),本文稍后將對此進行詳細討論。
垃圾收集
許多垃圾回收(GC)算法在與堆中存在的對象的平方成比例的時間內完成。 因此,例如,如果我們將堆上的對象數量增加一倍,則可以預期GC將花費四倍的時間才能完成。
另一方面,如果我們創建的對象增加了64倍,那么預期的預期GC時間將增加1,024倍。 這有效地阻止了我們創造出巨大的
HashMap對象。
使用ChronicleMap我們可以放置新的關聯而無需擔心垃圾收集時間。
序列化器
堆內存與堆外內存之間的介體通常稱為
序列化器 。 ChronicleMap帶有許多針對大多數內置Java類型(例如Integer , Long , String等)的預配置序列化器。
在上面的示例中,我們使用了一個自定義的序列化程序,該序列化程序用于在堆內存和非堆內存之間來回轉換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; } } 上面的序列化器實現為無狀態單例,并且write()和read()方法中的實際序列化非常簡單。 唯一棘手的部分是,我們需要在
如果“ using”變量未引用實例化/重用的對象,則為read()方法。
如何安裝?
當我們想在項目中使用ChronicleMap時,只需在pom.xml文件中添加以下Maven依賴項,就可以訪問該庫。
< dependency > < groupId >net.openhft</ groupId > < artifactId >chronicle-map</ artifactId > < version >3.17.3</ version > </ dependency >如果您使用的是另一種構建工具(例如Gradle),則可以通過單擊此鏈接來了解如何依賴ChronicleMap 。
短篇小說
以下是ChronicleMap的一些屬性:
堆外存儲數據
幾乎總是比HashMap更高的內存效率
實現ConcurrentMap 不影響垃圾收集時間 有時需要一個序列化器 具有固定的最大條目大小 可以容納數十億個協會 是免費和開源的
翻譯自: https://www.javacodegeeks.com/2019/08/java-chroniclemap-part-1-go-off-heap.html
精打細算油鹽
總結
以上是生活随笔為你收集整理的精打细算油盐_Java:ChronicleMap第1部分,精打细算的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【视频】更轻更薄续航还能翻倍,华为智能眼
- 下一篇: 14 英寸铭凡 V3 锐龙平板曝光:首搭