lein uberjar 结果 "Method code too large!"

lein uberjar results in "Method code too large!"

我有一个使用 lein run 运行良好的 clojure 项目,但是 lein uberjar 结果是

java.lang.RuntimeException: Method code too large!, compiling:(some_file.clj:1:1)

对于同一个项目。我可以使用包含 2000 多个条目的大向量将其追溯到 some_file.clj,该向量定义如下:

(def x
  [[1 :qwre :asdf]
   [2 :zxcv :fafa]
   ...
])

从该向量中删除足够多的条目后,lein uberjar 编译没有问题。我怎样才能让 uberjar 完成它的工作,将所有条目留在向量中?

N.B。将 x 更改为常量 a la (def ^:const x 时,lein run 也会抛出 Method too large! 错误。顺便说一句,这个错误发生在 x 使用 的地方,所以只要定义常量就可以了,如果它没有被使用的话。

在您的情况下,它会在对 delay 的调用中包装该值以使其在首次使用时进行计算吗?

java class 文件中方法的大小是有限制的,并且 Clojure 中的每个顶级表单(通常)都会生成一个 class。

要解决此问题,安排生成和存储常量非常有用:

  • 在内存中通过在运行时计算它们
  • 在运行时可以读取的资源目录中
  • 不要提前编译生成class文件,这样会在程序启动时生成数据。

如果您使用 future 或制作记忆函数来获取数据,您将在程序启动时计算它并将其存储在内存中而不是 class 文件中。

放在resources目录下不受大小限制,编译时仍可计算

如果禁用 AOT 编译,则永远不会达到 class 限制,因为它会在程序启动时在加载时计算。

事实证明,提前编译是 lein runlein uberjar 之间的区别,因为我的 project.clj 包含(默认)行

:profiles {:uberjar {:aot :all}})

当我删除 :aot :all 时,uberjar 完成时没有抱怨 - 但也没有进行 aot 编译。所以我想我需要关闭 aot,或者想办法限制它以排除该向量。

但我希望能够拥有这个——尽管很大——向量 "literal",并且仍然可以编译所有内容。但也许将这个巨大的向量分解成一个在启动时读入的数据文件也是一个不错的主意。然后我就可以保持 :aot :all 设置不变。

Java 中方法的大小有 64kb 的限制。在您的情况下,创建大向量文字的方法似乎超出了此限制。

实际上,您可以使用名为 clj-java-decompiler 的精美库自行检查。这是一个使用 boot:

的简短示例
(set-env!
 :dependencies '[[com.clojure-goes-fast/clj-java-decompiler "0.1.0"]])

(require '[clj-java-decompiler.core :as d])

(d/decompile-form
 {}
 [[1 :qwre :asdf]
  [2 :zxcv :fafa]
  [3 :zxcv :fafa]])

;; ==> prints:
;;
;; // Decompiling class: cjd__init
;; import clojure.lang.*;
;; 
;; public class cjd__init
;; {
;;  public static final AFn const__10;
;;  
;;  public static void load() {
;;                             const__10;
;;                             }
;;  
;;  public static void __init0() {
;;                                const__10 = (AFn)Tuple.create((Object)Tuple.create((Object)1L, (Object)RT.keyword((String)null, "qwre"), (Object)RT.keyword((String)null, "asdf")), (Object)Tuple.create((Object)2L, (Object)RT.keyword((String)null, "zxcv"), (Object)RT.keyword((String)null, "fafa")), (Object)Tuple.create((Object)3L, (Object)RT.keyword((String)null, "zxcv"), (Object)RT.keyword((String)null, "fafa")));
;;                                }
;;  
;;  static {
;;          __init0();
;;          Compiler.pushNSandLoader(RT.classForName("cjd__init").getClassLoader());
;;          try {
;;               load();
;;               Var.popThreadBindings();
;;               }
;;          finally {
;;                   Var.popThreadBindings();
;;                   }
;;          }
;;  }
;; 

如您所见,有一个方法 __init0 可以为矢量文字创建实际的 Java 对象。如果向量中有足够的元素,该方法的大小很容易超过 64kb 的限制。

我认为,在您的情况下,解决此问题的最简单方法是将矢量放入文件,然后 slurp+读取此文件。像这样的东西:

文件 vector.edn:

[[1 :qwre :asdf]
 [2 :zxcv :fafa]
 ...
 ]

然后在您的代码中:

(def x (clojure.edn/read-string (slurp "vector.edn"))