【问题标题】:What is an overhead for creating Java objects from lines of csv file从 csv 文件行创建 Java 对象的开销是多少
【发布时间】:2019-11-11 15:10:10
【问题描述】:

代码读取 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&lt;UserModel&gt; current = currentStream.collect(toSet())

文件大小约为 500MB 我已经使用 jconsole 连接到服务器,发现堆大小在处理时从 200MB 增长到 1.8GB。

我不明白这个 x3 内存使用量是从哪里来的 - 我预计会出现 500MB 左右的峰值?

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

【问题讨论】:

  • 这不是反序列化。
  • 为什么要将整个文件读入内存呢?一次处理一行。
  • @user207421 返回Stream&lt;String&gt; 的方法不是将整个文件读入内存。
  • @user207421 根据 google:在计算机科学中,在数据存储的上下文中,序列化是将数据结构或对象状态转换为可以存储或传输并在以后重建的格式的过程。
  • 如果您希望减少内存使用量,请参阅my Answer 到类似的问题,其中我展示了使用Apache Commons CSV 库使用BufferedReader 逐步读取文件而不是加载一次完整的文件。通过不读取整个文件,您将节省一半的内存。但是,无论您如何阅读,对象集合总是比答案中描述的 CSV 文件的纯文本占用更多的八位字节。

标签: java garbage-collection jvm file-processing


【解决方案1】:

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

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

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

与使用数组/列表的普通迭代相比,流也有更多的开销(请参阅此处Java 8 stream objects significant memory usage

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

  • java 对象的最小大小是 16 个字节,其中前 12 个字节是 JVM “开销”:对象的类引用(使用 Compressed Oops 时为 4 个字节)+ 标记字(用于标识哈希码,Biased locking ,垃圾收集器)
  • 接下来的 4 个字节用于引用第一个“令牌”
  • 接下来的 12 个字节被 3 次引用使用,以引用第二个、第三个和第四个“令牌”
  • 最后 4 个字节是必需的,因为 Java Object Alignment 在 8 字节边界处(在 64 位架构上)

话虽如此,尚不清楚您是否使用了从文件中读取的所有数据——您从一行中解析了 4 个标记,但也许还有更多? 此外,您没有提到堆大小是如何“增长”的——如果它是堆的 commited 大小或 used 大小。 used 部分是活动对象实际“使用”的部分,commited 部分是 JVM 在某些时候分配的,但以后可能会被垃圾收集; used &lt; commited 在大多数情况下。

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

【讨论】:

  • 感谢您的解释。我查看了堆转储,发现有 > 10_000_000(我的文件有 10_000_000 行)HashMap.Node 实例。他们正在分配〜500MB。看起来一切都来自套装。其他开销来自 UserModel 对象——正好是 10_000_000,它正好占用 480MB——每个对象有 48 个字节大小。 Char[] 和 String 分别占用 400MB 和 280MB。
  • 单个Stream实例的内存开销无关。
【解决方案2】:

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

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

除了支持char[]byte[]中的实际数据之外,还有实际String对象的内存。

然后是您的 UserModel 对象。每个对象都有一个标头,引用通常是 8 个字节(可能是 4 个)。

最后并不是所有的堆都会被分配。当在任何特定时刻没有使用相当比例的内存时,GC 运行效率更高。一旦进程启动并运行,即使是 C malloc 也会导致大部分内存未使用。

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

      猜你喜欢
      • 2010-10-18
      • 2021-01-22
      • 1970-01-01
      • 2012-05-29
      • 2020-10-21
      • 2013-10-05
      • 2011-04-25
      • 1970-01-01
      相关资源
      最近更新 更多