Java 为什么使用流式API的jsonParser会占用更多内存

Java 为什么使用流式API的jsonParser会占用更多内存,java,json,jackson,Java,Json,Jackson,我有代码可以解析整个文件: ConcurrentHashMap<String, ValueClass<V>> space = new ConcurrentHashMap<String, ValueClass<V>> (); Map<String, ValueClass<V>> map = mapper.readValue(new FileReader(fileName)...); for each entry of map:

我有代码可以解析整个文件:

ConcurrentHashMap<String, ValueClass<V>> space = new ConcurrentHashMap<String, ValueClass<V>> ();
Map<String, ValueClass<V>> map = mapper.readValue(new FileReader(fileName)...);
for each entry of map:
      space.put(entry.getKey(), entry.value());
ConcurrentHashMap空间=新的ConcurrentHashMap();
Map Map=mapper.readValue(新文件读取器(文件名)…);
对于地图的每个条目:
space.put(entry.getKey(),entry.value());
用于解析文件的内存很大,远远大于文件本身的大小。为了节省内存,我决定用jsonparse流式API替换代码,如下所示:

            ConcurrentHashMap<String, ValueClass<V>> space = new ConcurrentHashMap<String, ValueClass<V>> ();
            JsonFactory f = new MappingJsonFactory();
            JsonParser jp = f.createJsonParser(new File(fileName);
            JsonToken current;
            current = jp.nextToken();
            if (current != JsonToken.START_OBJECT) {
                show error and return;
            }
            ObjectMapper mapper = new ObjectMapper();
      while (jp.nextToken() != JsonToken.END_OBJECT) {
           key = jp.getCurrentName();
           current = jp.nextToken();
           if (key != null) {
               if (current == JsonToken.START_OBJECT) {
                   mem_before=Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory();
                       value = mapper.readValue(jp, ValueClass.class); (1)
                   mem_after=Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory();
                   mem_diff = mem_after - mem_before; (2)
                       space.put(key, value); (3)
               }
               else jp.skipChildren();
           else jp.skipChildren();
      }
ConcurrentHashMap空间=新的ConcurrentHashMap();
JsonFactory f=新映射JsonFactory();
JsonParser jp=f.createJsonParser(新文件名);
杰森托肯电流;
当前=jp.nextToken();
if(当前!=JsonToken.START\u对象){
显示错误并返回;
}
ObjectMapper mapper=新的ObjectMapper();
while(jp.nextToken()!=JsonToken.END\u对象){
key=jp.getCurrentName();
当前=jp.nextToken();
if(key!=null){
if(当前==JsonToken.START\u对象){
mem_before=Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freemory();
value=mapper.readValue(jp,ValueClass.class);(1)
mem_after=Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freemory();
mem_diff=mem_after-mem_before;(2)
空格。放置(键、值);(3)
}
else jp.skipChildren();
else jp.skipChildren();
}
但是,它花费的内存甚至比一次解析整个文件要多得多。它显示内存的增加是由于(1)(我使用Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freemory在(1)之前和之后检测分配的内存以获得差异)

根据和其他文章的说法,使用流式API不应该节省内存吗

编辑

@斯塔克斯曼:谢谢你回答我。看来你对我们应该期待的内存消耗非常熟悉

我的目的是将内容从文件加载到内存,这样我就不需要从文件中搜索内容,所以我需要地图中的所有内容

我认为流式处理可能有帮助的原因是:如果将整个文件作为一个对象加载,我想除了为map变量:space消耗内存外,我们还需要一个大的额外内存来解析整个文件,这是一个很大的负担;但是通过使用流式处理,尽管我们仍然需要为map变量:space使用相同的内存,但我们只需要少量的内存额外的内存用于解析文件的每个条目,这个额外的内存很小,因为我们重复使用它来解析每个条目,而不是解析整个文件。它不节省内存吗?如果我错了,请纠正我

我知道内存大小可能大于文件大小。但首先,我不明白为什么它需要比文件大这么多(在我的情况下是文件大小的2.7倍);其次,我不明白为什么流式处理会花费更多内存(甚至是不使用流式处理的两倍).内存泄漏?但我看不出我的代码有任何问题。应该至少不超过2.7次


此外,您是否知道如何估计对象的内存消耗,例如“我的地图变量空间”的每个条目?

如果您解释了您试图实现的目标,这会有所帮助。例如,您是否始终绝对需要内存中的所有值?或者是否可以一次处理一个值,并避免构建该值
使用所有键和值映射

如果不解释您要在那里做什么,您的第二次尝试就没有多大意义;但从本质上讲,所使用的内存量应该是值POJO、键和包含它们的
Map
。这可能多于或少于输入JSON,但实际上取决于您处理的内容的类型。例如,字符串l在JVM中比在JSON文件中消耗内存:UTF-8中的大多数字符是单字节的,而JVM中的每个
char
是16位值(UCS-2编码)。此外,虽然JSON字符串有3或4字节的开销(引号、分隔符),但是
java.lang.String
的内存开销更像是每个字符串16或24字节

类似地,
Map
s为JSON文件不需要的结构消耗了相当多的内存。JSON每个条目有两个分隔符,可能是6-8字节(取决于使用的缩进)。另一方面,Java映射除了字符串键之外,还有更大的每个条目开销(可能是16字节?)和POJO值


所以:总而言之,内存中的对象可能比JSON文件本身所包含的内容占用更多的内存,这种情况并不少见。

ObjectMapper#readValue()
不用于流式处理。它用于在内存中创建结构。我使用的是完整数据绑定(POJO)。看。这不是解析类的正确方法吗?那么正确的方法是什么?谢谢。在检查freeMemory()之前,您是否强制执行gc?您检查了哪些点?另外,使用jconsole或jvisualvm等工具可能更容易监视堆的使用。@user389955使用流式处理时,您应该手动创建对象,正如您看到的标记一样。尽管我认为使用非流式处理读取时,您的内存使用率不会(大大)降低。(每个对象都需要额外的内存,所以我不会期望任何接近UTF-8编码文件大小的东西。可能会大2-5倍。)@Gábor Bakos:我用第(2)行和第(3)行更新了我的原始帖子。第(2)行是我收集分配内存的方式。事实上,如果我删除第(3)行,我甚至会得到负内存。所以我怀疑第(3)行导致内存泄漏,导致内存过大。您认为这可能是原因吗?