这似乎是 Java 内存中对象表示效率低下的简单组合,加上一些明显的长期对象引用,导致某些集合无法及时进行垃圾收集,以便新的collect()调用覆盖旧的一个到位。
我尝试了一些选项,对于包含约 4M 行的 256MB 示例文件,我确实重现了您的行为,第一次收集很好,但第二次使用时 OOMSPARK_MEM=1g
。然后我设置SPARK_MEM=4g
相反,然后我可以 ctrl+c 并重新运行test <- collect(lines)
我想要多少次都可以。
一方面,即使引用没有泄漏,请注意,在第一次运行之后test <- collect(lines)
,变量test
保存着巨大的线数组,当你第二次调用它时,collect(lines)
执行before最终被分配到test
变量,因此在任何简单的指令排序中,都无法对旧内容进行垃圾收集test
。这意味着第二次运行将使 SparkRBackend 进程同时持有整个集合的两个副本,从而导致您看到的 OOM。
为了诊断,我在主服务器上启动了 SparkR 并首先运行
dhuo@dhuo-sparkr-m:~$ jps | grep SparkRBackend
8709 SparkRBackend
我也检查过top
它使用了大约 22MB 内存。我获取了一个堆配置文件jmap
:
jmap -heap:format=b 8709
mv heap.bin heap0.bin
然后我跑了第一轮test <- collect(lines)
此时运行top
使用约 1.7g RES 内存进行展示。我抓住了另一个堆转储。最后我也尝试过test <- {}
摆脱引用以允许垃圾收集。完成此操作后,并打印出来test
并显示它是空的,我抓起另一个堆转储并注意到 RES 仍然显示 1.7g。我用了jhat heap0.bin
分析原始堆转储,得到:
Heap Histogram
All Classes (excluding platform)
Class Instance Count Total Size
class [B 25126 14174163
class [C 19183 1576884
class [<other> 11841 1067424
class [Lscala.concurrent.forkjoin.ForkJoinTask; 16 1048832
class [I 1524 769384
...
运行收集后,我有:
Heap Histogram
All Classes (excluding platform)
Class Instance Count Total Size
class [C 2784858 579458804
class [B 27768 70519801
class java.lang.String 2782732 44523712
class [Ljava.lang.Object; 2567 22380840
class [I 1538 8460152
class [Lscala.concurrent.forkjoin.ForkJoinTask; 27 1769904
即使在我取消之后test
,它保持大致相同。这向我们展示了 2784858 个 char[] 实例,总大小为 579MB,还有 2782732 个 String 实例,大概将这些 char[] 放在它上面。我按照参考图一路向上,得到了类似的东西
char[] -> String -> String[] -> ... -> 类 scala.collection.mutable.DefaultEntry -> 类 [Lscala.collection.mutable.HashEntry; -> 类 scala.collection.mutable.HashMap -> 类 edu.berkeley.cs.amplab.sparkr.JVMObjectTracker$ -> java.util.Vector@0x785b48cd8 (36 字节) -> sun.misc.Launcher$AppClassLoader@0x7855c31a8 ( 138字节)
然后 AppClassLoader 就有数千个入站引用。因此,沿着这条链的某个地方,应该有一些东西已经删除了它们的引用,但没有这样做,导致整个收集到的数组位于内存中,而我们尝试获取它的第二个副本。
最后回答一下关于挂机后挂起的问题collect
,看来这与数据不适合 R 进程的内存有关;这是与该问题相关的线程:https://www.mail-archive.com/[电子邮件受保护]/msg29155.html
我确认使用只有几行的较小文件,然后运行collect
确实不挂。