You're right that Clojure's own bootstrap is a huge part of the startup cost and it's not just JVM bad. But I'd push back a little on the framing that the JVM gets blamed unfairly because the JVM still does have a real startup hit compared to something like a Chez Scheme binary.
Chez and produces native binaries with startup in the microsecond range and around 8 to 13 megs in size. That's not just slightly faster than JVM Clojure for cold starts. The JVM is designed around a model where you have a long-lived process like an app server. Even if you optimize Clojure's bootstrap down to zero, you're still hauling around a heavy runtime.
And GraalVM native-image comes with its own set of trade-offs since it's based on a closed-world assumption, which means dynamic class loading, reflection, serialization, and so on all need to be configured at build time. So, you can't do eval at runtime, and Babashka ends up having to use an interpreter for anything dynamic. Bytecode isn't available at runtime either, so you lose a bunch of the debugging and monitoring tools as well.
The compile time is also brutal. Last I tried making native-image it took several minutes to build, and you have to be careful with how you write code or what libraries you use. It's not just a different compile target that works as a drop in. There's a significant cost if you're iterating that quickly makes you miss the REPL.
So yeah, the JVM isn't the only reason Clojure starts slowly, but it's still a major contributing factor. Also, as I point out in the post, there are lots of negative connotations with JVM in the broader programming community. People have been preaching that the JVM is not a problem for years now, but it's still shunned. An perception is just as much a barrier as a technical limitation.
Jolt shows what's possible when you drop the JVM entirely.
Chez and produces native binaries with startup in the microsecond range and around 8 to 13 megs in size. That's not just slightly faster than JVM Clojure for cold starts. The JVM is designed around a model where you have a long-lived process like an app server. Even if you optimize Clojure's bootstrap down to zero, you're still hauling around a heavy runtime.
And GraalVM native-image comes with its own set of trade-offs since it's based on a closed-world assumption, which means dynamic class loading, reflection, serialization, and so on all need to be configured at build time. So, you can't do eval at runtime, and Babashka ends up having to use an interpreter for anything dynamic. Bytecode isn't available at runtime either, so you lose a bunch of the debugging and monitoring tools as well.
The compile time is also brutal. Last I tried making native-image it took several minutes to build, and you have to be careful with how you write code or what libraries you use. It's not just a different compile target that works as a drop in. There's a significant cost if you're iterating that quickly makes you miss the REPL.
So yeah, the JVM isn't the only reason Clojure starts slowly, but it's still a major contributing factor. Also, as I point out in the post, there are lots of negative connotations with JVM in the broader programming community. People have been preaching that the JVM is not a problem for years now, but it's still shunned. An perception is just as much a barrier as a technical limitation.
Jolt shows what's possible when you drop the JVM entirely.