从 csv 文件行创建 Java objects 的开销是多少

What is an overhead for creating Java objects from lines of csv file

代码读取 CSV 文件的行,如:

Stream<String> strings = Files.lines(Paths.get(filePath))

然后它映射映射器中的每一行:

List<String> tokens = line.split(","); return new UserModel(tokens.get(0), tokens.get(1), tokens.get(2), tokens.get(3));

终于收藏了:

Set<UserModel> current = currentStream.collect(toSet())

文件大小约为 500MB 我已经使用 jconsole 连接到服务器,并看到在处理过程中堆大小从 200MB 增加到 1.8GB。

我不明白这个 x3 内存使用量从何而来 - 我预计会有 500MB 左右的峰值?

我的第一印象是因为没有节流,垃圾收集器根本没有足够的时间进行清理。 但我尝试使用番石榴速率限制器让垃圾收集器有时间完成它的工作,但结果是一样的。

您的代码将整个文件读入内存。然后开始将每一行拆分为一个数组,然后为每一行创建自定义 class 的对象。所以基本上你的文件中的每一行都有 3 个不同的 "memory usage" !

虽然有足够的内存可用,但 jvm 可能根本不会浪费时间 运行 垃圾收集器,同时将您的 500 兆字节转换为三种不同的表示形式。因此,您可能 "triplicate" 文件中的字节数。至少在 gc 启动并丢弃不再需要的文件行和拆分数组之前。

可能 String 实现使用的是 UTF-16,而文件可能使用的是 UTF-8。假设所有美国 ASCII 字符,这将是大小的两倍。但是,我相信现在 JVM 倾向于使用 Strings 的紧凑形式。

另一个因素是 Java object 倾向于分配在一个很好的循环地址上。这意味着有额外的填充。

除了支持 char[]byte[].

中的实际数据外,还有实际 String object 的内存

然后是你的 UserModel object。每个 object 都有一个 header 并且引用通常是 8 字节(可能是 4)。

最后,不会分配所有堆。当相当一部分内存在任何特定时刻未被使用时,GC 运行效率更高。一旦进程启动并且 运行.

即使是 C malloc 也会以大量未使用的内存结束

Tom Hawtin 提出了很好的观点 - 我只是想扩展它们并提供更多细节。

Java 由于 java 对象头(见下文)开销和内部字节数组,字符串至少占用 40 个字节的内存(用于空字符串)。 这意味着 non-empty 字符串(1 个或多个字符)的最小大小为 48 个字节。

如今,JVM 使用 Compact Strings,这意味着 ASCII-only 字符串每个字符仅占用 1 个字节 - 之前每个字符最少占用 2 个字节。 这意味着如果您的文件包含超出 ASCII 集的字符,则内存使用量会显着增加。

与使用 arrays/lists 的普通迭代相比,流也有更多的开销(参见此处 Java 8 stream objects significant memory usage

我猜你的 UserModel 对象在每一行的顶部至少增加了 32 字节的开销,因为:

  • java 对象的最小大小为 16 字节,其中前 12 个字节是 JVM "overhead":对象的 class 引用(Compressed Oops are used) + the Mark word (used for identity hash code, Biased locking 时为 4 字节,垃圾收集器)
  • 接下来的 4 个字节由对第一个 "token"
  • 的引用使用
  • 接下来的 12 个字节被对第二个、第三个和第四个的 3 个引用使用 "token"
  • 最后 4 个字节是必需的,因为 Java Object Alignment 在 8 字节边界(在 64 位架构上)

也就是说,不清楚您是否使用了从文件中读取的所有数据 - 您从一行中解析了 4 个标记,但也许还有更多? 此外,您没有提到堆大小 "grew" 的确切大小 - 如果它是堆的 commited 大小或 used 大小。 used 部分是活动对象实际 "used" 的部分,commited 部分是 JVM 在某个时候分配的部分,但可能是 garbage-collected 之后的部分; used < commited 在大多数情况下。

您必须拍摄堆快照以了解 UserModel 的结果集实际占用了多少内存,与文件的大小进行比较实际上很有趣。