If you replace functions at runtime frequently, the way this poster wanted to do for the purposes of genetic-programming, will Clojure/JVM GC old code?
2 Answers
When in doubt, ask the computer! I wrote a quick test:
(ns tst.demo.core
(:use demo.core tupelo.core tupelo.test))
(defn create-fn
[n]
(let [fname (symbol (format "add-%d" n))
body (vector '[x] (list '+ n 'x))
fn-form (apply list 'clojure.core/defn fname body)
fn-ref (eval fn-form)]
fn-ref))
(defn create-and-call-fn
[n]
(let [fn-ref (create-fn n)
fn-result (fn-ref 3)]
(when-not (= (+ 3 n) fn-result)
(throw (ex-info "bad result" (vals->map n fn-result))))))
(dotest
(let [f5 (create-fn 5)
f5-result (f5 3)]
(is= 8 f5-result))
(dotimes [i 99999]
(when (zero? (mod i 100)) (spyx i))
(create-and-call-fn i)))
and up to 100K function creations later, it still works. YMMV!
The above is based on this template project.
5 Comments
eval a function, you generate a new class, not just a new object. Whether that class gets cleaned up is less obvious than whether some random lambda does.The answer is yes, the generated code should get collected at some point at the future. When it does is not specified; it may happen late, or never.
The other thing to watch out for is (particularly when using code from another eval) is whether you have circular references in the code that gets generated which prevent them from being unloaded. Typically when a ClassLoader gets released, all of its classes get released as well -- and some interpreted/JITd languages on top of the JVM use this to allow the code to be recycled. When the classes are unloaded, any compiled code is also jettisoned -- but if you have a reference to an instance of that class of that classloader (say, in the ThreadLocalStorage) then it will pin the instance, then the class, then the classloader, so it's possible that they will still leak.