4

在我工作的地方,曾经有每个文件超过百万行的文件。尽管服务器内存超过 10GB,而 JVM 内存为 8GB,但有时服务器会挂起片刻并阻塞其他任务。

我分析了代码,发现虽然文件读取内存使用率经常增加千兆字节(1GB 到 3GB),然后突然恢复正常。似乎这种频繁的高低内存使用会挂起我的服务器。当然,这是由于垃圾收集。

我应该使用哪个 API 来读取文件以获得更好的性能?

现在我BufferedReader(new FileReader(...))用来阅读这些 CSV 文件。

过程:我如何读取文件?

  1. 我逐行读取文件。
  2. 每行都有几列。基于我相应地解析它们的类型(double中的成本列,int中的访问列,String中的关键字列等)。
  3. 我在 HashMap 中推送符合条件的内容(访问 > 0),最后在任务结束时清除该 Map

更新

我读取 30 或 31 个文件(一个月的数据)并将符合条件的文件存储在地图中。后来这张地图被用来在不同的桌子上找到一些罪魁祸首。因此读取是必须的,存储数据也是必须的。虽然我现在已经将 HashMap 部分切换到 BerkeleyDB,但是读取文件时的问题是相同的,甚至更糟。

4

3 回答 3

10

BufferedReader 是用于此目的的两个最佳 API 之一。如果你真的在文件读取方面遇到问题,另一种方法可能是使用NIO中的东西来内存映射你的文件,然后直接从内存中读取内容。

但你的问题不在于读者。您的问题是每个读取操作都会创建一堆新对象,很可能是您在阅读后所做的事情。

您应该考虑清理您的输入处理,着眼于减少您创建的对象的数量和/或大小,或者在不再需要时更快地摆脱对象。是否可以一次处理一行或一大块文件,而不是将整个文件吸入内存进行处理?

另一种可能性是摆弄垃圾收集。你有两种机制:

  • 每隔一段时间显式调用一次垃圾收集器,比如每 10 秒或每 1000 个输入行或某事。这将增加 GC 完成的工作量,但每次 GC 花费的时间会更少,您的内存不会膨胀太多,因此希望对服务器其余部分的影响会更小。

  • 摆弄 JVM 的垃圾收集器选项。这些在 JVM 之间有所不同,但java -X应该会给您一些提示。

更新:最有希望的方法:

您真的需要一次将整个数据集放在内存中进行处理吗?

于 2009-11-28T13:59:57.687 回答
5

我分析了代码,发现虽然文件读取内存使用率经常增加千兆字节(1GB 到 3GB),然后突然恢复正常。似乎这种频繁的高低内存使用会挂起我的服务器。当然,这是由于垃圾收集。

使用BufferedReader(new FileReader(...))不会导致这种情况。

我怀疑问题在于您正在将行/行读入数组或列表,处理它们然后丢弃数组/列表。这将导致内存使用量增加然后再次减少。如果是这种情况,您可以通过在阅读时处理每一行/行来减少内存使用量。

编辑:我们一致认为问题在于用于表示内存中文件内容的空间。一个巨大的内存哈希表的替代方法是回到我们在以千字节为单位测量计算机内存时使用的旧“排序合并”方法。(我假设处理由您使用键 K 进行查找以获取关联的行 R 的步骤控制。)

  1. 如有必要,预处理每个输入文件,以便它们可以在键 K 上排序。

  2. 使用高效的文件排序实用程序将所有输入文件按 K 上的顺序排序。您希望使用将使用经典合并排序算法的实用程序。这会将每个文件分成更小的块,可以在内存中排序,对块进行排序,将它们写入临时文件,然后合并排序的临时文件。UNIX / Linuxsort实用程序是一个不错的选择。

  3. 并行读取已排序的文件,从所有文件中读取与每个键值相关的所有行,对其进行处理,然后继续执行下一个键值。

实际上,我对使用 BerkeleyDB 没有帮助感到有点惊讶。但是,如果分析告诉您大部分时间都花在了构建数据库上,那么您可以通过在构建数据库之前将输入文件(如上!)按升序排序来加快速度。(在创建基于文件的大型索引时,如果按键顺序添加条目,您将获得更好的性能。)

于 2009-11-28T13:58:23.273 回答
1

尝试使用以下 vm 选项来调整 gc(并进行一些 gc 打印):

-verbose:gc -XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode -XX:+PrintGCDetails -XX:+PrintGCTimeStamps
于 2011-04-05T05:23:01.327 回答