【问题标题】:lein uberjar results in "Method code too large!"lein uberjar 导致“方法代码太大!”
【发布时间】:2018-04-25 02:35:41
【问题描述】:

我有一个使用 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 完成它的工作,将所有条目留在向量中?

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

【问题讨论】:

    标签: clojure leiningen


    【解决方案1】:

    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"))
    

    【讨论】:

    • 是的,这就是我现在正在做的事情:slurping edn 资源文件。
    【解决方案2】:

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

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

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

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

    【讨论】:

      【解决方案3】:

      在您的情况下是否可以将值包装在对delay 的调用中,以便在首次使用时对其进行计算?

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

      要解决这个问题,安排生成和存储常量是很有用的:

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

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

      如果放在资源目录下,则不受大小限制,仍然可以在编译时计算。

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

      【讨论】:

      • 我尝试将其更改为(def x-delay (delay [...])) (def x @c-delay)(这样我就不必更改散布在代码库周围的使用x的代码,但我仍然得到一个`方法代码大!”错误。
      • 在顶层取消引用交易的未来将等同于在顶层没有延迟地定义它。我将扩展答案以进一步讨论。
      • 这就是我所担心的。最后,按照 OlegTheCat 的建议,我现在正在吃 edn 资源。
      猜你喜欢
      • 1970-01-01
      • 2016-03-06
      • 2016-06-06
      • 1970-01-01
      • 1970-01-01
      • 2018-06-23
      • 2019-01-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多