Clojure Leining REPL 出现内存不足错误 Java 堆空间

5

我正在尝试解析一个相当小的(<100MB)xml文件,使用以下方法:

(require '[clojure.data.xml :as xml]
         '[clojure.java.io :as io])

(xml/parse (io/reader "data/small-sample.xml"))

我遇到了一个错误:

OutOfMemoryError Java heap space
    clojure.lang.Numbers.byte_array (Numbers.java:1216)
    clojure.tools.nrepl.bencode/read-bytes (bencode.clj:101)
    clojure.tools.nrepl.bencode/read-netstring* (bencode.clj:153)
    clojure.tools.nrepl.bencode/read-token (bencode.clj:244)
    clojure.tools.nrepl.bencode/read-bencode (bencode.clj:254)
    clojure.tools.nrepl.bencode/token-seq/fn--3178 (bencode.clj:295)
    clojure.core/repeatedly/fn--4705 (core.clj:4642)
    clojure.lang.LazySeq.sval (LazySeq.java:42)
    clojure.lang.LazySeq.seq (LazySeq.java:60)
    clojure.lang.RT.seq (RT.java:484)
    clojure.core/seq (core.clj:133)
    clojure.core/take-while/fn--4236 (core.clj:2564)

这是我的project.clj文件:

(defproject dats "0.1.0-SNAPSHOT"
  ...
  :dependencies [[org.clojure/clojure "1.5.1"]
                [org.clojure/data.xml "0.0.7"]
                [criterium "0.4.1"]]
  :jvm-opts ["-Xmx1g"])

我尝试在我的 .bash_profile 文件中设置 LEIN_JVM_OPTS 和 JVM_OPTS,但是没有成功。

当我尝试以下的 project.clj 时:

(defproject barber "0.1.0-SNAPSHOT"
  ...
  :dependencies [[org.clojure/clojure "1.5.1"]
                [org.clojure/data.xml "0.0.7"]
                [criterium "0.4.1"]]
  :jvm-opts ["-Xms128m"])

I get the following error:

Error occurred during initialization of VM
Incompatible minimum and maximum heap sizes specified
Exception in thread "Thread-5" clojure.lang.ExceptionInfo: Subprocess failed {:exit-code 1}

有什么办法可以增加我的Leiningen REPL的堆大小?
谢谢。

你是否将一些数据(XML解析结果)存储在数组中?如果是,它有多大? - Chiron
你是从 REPL 中调用第二行吗? - Leon Grapenthin
Chiron: 目前尚未将XML存储在任何数据结构中,只是像我在帖子中提到的那样调用解析方法。 Igrapenthin: 是的,我正在从REPL中调用解析行。 该文件大小为50MB,未压缩。 - Nicolas M.
1
正如我在答案中所指出的,repl 顶层返回的所有内容都会被存储(即使它们本来是惰性的也会被完全评估),首先存储为 *1,然后是 *2,以此类推。 - noisesmith
2个回答

4
任何在repl顶层求值的表单都会完全实现,作为Read-Eval-Print-Loop中的打印步骤的结果。它也被存储在堆中,这样您以后可以通过*1访问它。
如果您将返回值存储如下: (def parsed (xml/parse (io/reader "data/small-sample.xml")))
即使文件大小为数百兆字节(我已在本地验证),此操作将立即返回。然后,您可以通过迭代返回的clojure.data.xml.Element树来遍历结果,因为该树在从输入流解析时被完全实现。
如果您不保留元素(通过绑定使它们仍然可访问),则可以在不使用比保存xml树的单个节点所需的ram更多的情况下遍历整个结构。
user> (time (def n (xml/parse (clojure.java.io/reader "/home/justin/clojure/ok/data.xml"))))
"Elapsed time: 0.739795 msecs"
#'user/n
user> (time (keys n))
"Elapsed time: 0.025683 msecs"
(:tag :attrs :content)
user> (time (-> n :tag))
"Elapsed time: 0.031224 msecs"
:catalog
user> (time (-> n :attrs))
"Elapsed time: 0.136522 msecs"
{}
user> (time (-> n :content first))
"Elapsed time: 0.095145 msecs"
#clojure.data.xml.Element{:tag :book, :attrs {:id "bk101"}, :content (#clojure.data.xml.Element{:tag :author, :attrs {}, :content ("Gambardella, Matthew")} #clojure.data.xml.Element{:tag :title, :attrs {}, :content ("XML Developer's Guide")} #clojure.data.xml.Element{:tag :genre, :attrs {}, :content ("Computer")} #clojure.data.xml.Element{:tag :price, :attrs {}, :content ("44.95")} #clojure.data.xml.Element{:tag :publish_date, :attrs {}, :content ("2000-10-01")} #clojure.data.xml.Element{:tag :description, :attrs {}, :content ("An in-depth look at creating applications \n      with XML.")})}
user> (time (-> n :content count))
"Elapsed time: 48178.512106 msecs"
459000
user> (time (-> n :content count))
"Elapsed time: 86.931114 msecs"
459000
;; redefining n so that we can test the performance without the pre-parsing done when we counted
user> (time (def n (xml/parse (clojure.java.io/reader "/home/justin/clojure/ok/data.xml"))))
"Elapsed time: 0.702885 msecs"
#'user/n
user> (time (doseq [el (take 100 (drop 100 (-> n :content)))] (println (:tag el))))
:book
:book
.... ;; output truncated
"Elapsed time: 26.019374 msecs"
nil
user> 

请注意,只有在我首次请求n内容的计数时(从而强制整个文件进行解析),才会出现巨大的时间延迟。如果我在结构的子部分上使用doseq,这将非常快速地完成。

谢谢您的回答。我理解惰性求值的要点,但在我的情况下,对于 (time (-> n :content count)) 的调用也会导致 java.lang.OutOfMemoryError: Java heap space 错误。总体上,我正在尝试找到一种方法来获得超过50MB的堆空间,但我无法弄清楚。 - Nicolas M.
错误信息"Incompatible minimum and maximum heap sizes specified"告诉我,某处设置了很低的最大值,你需要绕过或覆盖。导致该错误的选项指定了一个起始堆大小(-Xms)但没有最大堆大小(-Xmx)。 - noisesmith
此外,我不知道你想做什么,但通常会有一些减少的方法来完成你想要的目标,而无需一次性将整个数据集加载到内存中。 - noisesmith
我现在正在处理数据。一个目标是从XML中提取数据并将其放入具有适当关联设置的结构化数据库中。 当我同时指定最小堆和最大堆时,出现了类似的错误。您知道在哪里检查默认堆大小配置吗? 是否有办法从lein repl知道堆大小?非常感谢您的帮助! - Nicolas M.
1
(.maxMemory (java.lang.Runtime/getRuntime)) 将显示可用的最大内存,.totalMemory 也可用等等。http://docs.oracle.com/javase/6/docs/api/java/lang/Runtime.html - noisesmith
显示剩余3条评论

2

我对lein不是很了解,但在mvn中你可以进行以下操作:

mvn  -Dclojure.vmargs="-d64 -Xmx2G" clojure:nrepl

(我认为这没关系,但我总是看到它用大写字母G,区分大小写吗?)

将100MB的数据加载到内存中应该没有问题。 我经常通过我的项目路由GB级别的数据。

对于大堆还是使用64位版本的服务器,这似乎是他们在这里所做的:

使用Leiningen的JVM选项

我认为更大的问题是,按照你的写法,这可能在编译时被评估。 你需要将该调用包装在函数中,并推迟其执行。 我认为编译器正在尝试读取该文件,这可能不是您想要的。 我知道使用mvn时,您会获得不同的编译与运行内存设置,您可能也会获得这些设置。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接