糖尿病康复,内容丰富有趣,生活中的好帮手!
糖尿病康复 > 精打细算油盐_Java:ChronicleMap第1部分 精打细算

精打细算油盐_Java:ChronicleMap第1部分 精打细算

时间:2019-03-25 07:48:09

相关推荐

精打细算油盐_Java:ChronicleMap第1部分 精打细算

精打细算油盐

用数百万个对象填充HashMap会很快导致诸如内存使用效率低下,性能低下和垃圾回收等问题。 了解如何使用堆外CronicleMap,其中可以包含数十亿个对象,而对堆的影响很小或没有。

当我们要使用中小型数据集时,内置的Map实现(例如HashMapConcurrentHashMap是出色的工具。 但是,随着数据量的增长,这些

正如开放源代码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条目,都需要在堆上创建LongHashMap$NodePoint对象。 还有许多创建了HashMap$Node对象的数组。 这些对象和数组总共消耗了88,515,056字节的堆内存。 因此,每个条目平均消耗88.5个字节。

注意:额外的2429HashMap$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类型(例如IntegerLongString等)的预配置序列化器。

在上面的示例中,我们使用了一个自定义的序列化程序,该序列化程序用于在堆内存和非堆内存之间来回转换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不影响垃圾收集时间 有时需要一个序列化器 具有固定的最大条目大小 可以容纳数十亿个协会 是免费和开源的

翻译自: //08/java-chroniclemap-part-1-go-off-heap.html

精打细算油盐

如果觉得《精打细算油盐_Java:ChronicleMap第1部分 精打细算》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。