The recent releases of Java 8u201/8u202 and 11.0.2 include a fix for a 0-day vulnerability that could occur while running a class static initializer. The fix included in these releases prevents JVM optimization during the static initializer (before the class is initialized). Doing significant work in the static initializer can see dramatic reduction in performance from the previous versions 8u192 / 11.0.1.
Clojure supports a well-known file user.clj that is loaded during runtime initialization (in the clojure.lang.RT class). This is sometimes used to load tools during development. Currently user.clj is loaded in the scope of RT's static inititializer, so any Clojure code run in user.clj is significantly slower.
One common pattern popularized by Stuart Sierra is the "reloaded" pattern which will load and initialize the entire server from the user.clj. Users of this pattern are likely to see dramatic startup time performance issues with these Java releases.
- http://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8219233 - bug filed for this issue
Repro: see https://github.com/puredanger/slow-user-load
Move the clojure.core init from doInit() into the RT static initializer, and then force explicit invocation of RT.doInit() in all expected RT entry points:
- clojure.main/main - main launcher stub
- clojure.java.api.Clojure<clinit> - Java API (this path will still incur the cost via Clojure<clinit>)
- clojure.lang.Compile/main - used only in Clojure's own build afaik
- clojure.lang.Util/loadWithClass - gen-class/AOT class static initializer
Other alternatives considered:
1) Another option is to take the hints from the scope of the issue at https://cl4es.github.io/2019/02/21/Cljinit-Woes.html and avoid calling static methods on an uninitialized class. The basic idea here is to move all of the RT static methods into an inner class RT.Impl. That class can fully load so all methods are initialized. The RT static init can then just invoke doInit() on it. All references to RT static methods in Clojure must be rerouted from RT to RT.Impl. The existing RT methods are left and forward to RT.Impl - this allowed previously compiled Clojure classes to continue to work (or any stray RT calls made by advanced Clojure code).
Patch: clj-2484-nested-class.patch - this works, but is an enormous patch as it touches all of RT and every call to RT in Clojure.
2) Do nothing and wait for Java to mitigate the perf impact. At this point, it does not look like a simple fix.