At FastComments we run our E2E tests on the new instance to JIT the app. Before the Jit API calls can take 100ms, and after 10-30ms. Still, that is fast enough that most people wouldn't notice...
Also, the problem is not Java. Your application probably has way too many abstractions for a simple login page.
There is a huge DI framework full of reflection and proxy classes, then a complex JIT to make the framework performant and now finally a scheduled warmup/load test phase to make the JIT work.
It seems like the same or better performance could be archieved with far less complexity by using an AOT compiled language.
Or use reflection free frameworks like Micronaut or Quarkus and enjoy the same productivity. The culprit here is no java, the spring framework.
I was never really a fan of DI.
Part of my point is your application should be fast enough before the JIT kicks in.
Ideally, you should also do performance testing. Incorporating that with your release go or no go is a great approach, IMO, if you do releases very often.
Because it's probably a whole lot less work to add this new deployment mechanism than to completely change languages. Even for new projects there are tons of other things to factor in when picking a language; a crutch in one area can sometimes be the right call.
C++ went through this 25+ years ago: runtime binding, what in C++ is virtual functions, is a niche technique. Most C++ programs don't use it at all, or use it in only one or two spots. When it is the right thing, it makes the work convenient, but it really is just a dance with function pointers. In C++, templates do the heavy lifting.
Java never offered any other support for organizing programs, so inheritance and virtual functions have been your go-to for everything, no matter how bad the fit. In a static call there is only one bit of code to run, and it never changes over the life of the program. Just like almost everything, really, except here your runtime knows up front.
It was always a dumb choice to make member functions default to virtual semantics, when they almost always don't need it, and it just costs performance to no purpose. That is what comes out of treating language design as a marketing exercise: Java's designers really (and openly) cared less than nothing about object-oriented programming. They thought people really ought to be coding Lisp. Forcing runtime binding was a way to sneak in something a little bit lispy, and maybe get people used to production code running no faster than Lisp.
If you actually don't need it (i. e., your hot methods are never overridden), then the JIT will trivially compile those "virtual" method calls as non-virtual ones. It has all the information it needs, since it knows what classes are loaded. It can invalidate its code if necessary, if you load more classes that do add overrides. So no, it does not cost performance at the call site. It does cause the compiler to do work, but nothing fancy.
But isn't that the thrust of this article? Of course the JIT can optimise a monomorphic call-site. The question is, in reality, what percentage of the time will it be optimised for your users?
Java simplified things for performance but ended up with a much weaker and less expressive system, and probably didn't go hard enough for static performance either. Still, it's less of a performance issue than the lack of value types.
It sounds to me like the problem isn’t the VM it’s the insane amount of framework code you have to be initialized. This can be optimized with Java via class data sharing and snapshotting but if your code is all written in Kotlin and you toss Spring into the trash can In favor of say, Dagger or hand crafted DI, most of your problems would go away.
Besides, Kotlin/Native can compile and run without a VM.
See https://august.nagro.us/jvm-startup.html for examples of how to optimize startup without Prewarming JIT.
This is a language construct and not enforced within the byte code. There have historically been reasons to allow final to be ignored, such as serialization. Instead the JIT will optimize and deoptimize based on runtime behavior, allowing it to cover cases where overriding is designed for but not done so in the actual runtime environment. There was a nice presentation on "truly final fields" at the 2018 language summit [1].
With GWT, we had global information, so we could promote methods to static by class hierarchy analysis.
For a Kotlin class, even with dynamic loading, if the class is not marked 'open', final methods not implementing an interface, or private methods, can be converted into static methods.
e.g.
class A(val x: Int) {
fun print() = x
}
can be transformed into class A(val x: Int) {
fun print() = printStatic(x)
companion object {
fun printStatic(self: A) = self.x
}
}
And any call site with access to the original signature, e.g. val a = A()
println(a.print())
could be rewritten safely as val a = A()
println(A.printStatic(a))
Now, the current Kotlin compiler frontend or backend may not do any of these, but it could.There's nothing in the compiler language itself that blocks such optimizations, especially if you're willing to limit reflection (as is the case with Kotlin MPP code), or admit a LTO pass.
This sounds like a design flaw in the architecture. You don't tear down a house to replace a lightbulb.
Which analogy you consider spurious likely depends on what kind of theatres of IT war you've been in.
I wonder how they instrument their Go and Rust programs. If they decide not to, maybe it's not that important for the Java version of the same code either.
Something like a tool that runs as part of the build process and modifies the source code before it gets passed to the compiler.
Not having worked in Go or Rust but having done a lot of work in Java and C/C++, I'm curious how this works out in practice. My experience with developers who default to static method calls rather than objects in Java or C++ is that they also default to static (and therefore global) data as well. Of course, you don't have to do that: you can pass pre-transaction data structures to the static functions and let them operate on them, but that's what object-oriented programming is for in the first place.
You can make "virtual method" calls in Rust (dynamic dispatch through a "trait object", a vtable). This is explicit though, the type will look like `Box<dyn MyInterface>` where Java says `MyInterface` (box = object allocate on heap, dyn = virtual method calls).
I think you might be misunderstanding what "static method call" means: in this context, it means a method which is not "virtual" (in C++ parlance), not a method which is "static" (in C++ parlance, e.g. not called on an object).
Other JVMs like J9 have had AOT support for decades now, its not "lipstick on a pig". There's plenty of material from previous JVMLS meetups about AOT.
Indeed, the original Sysdig report that the chart comes from makes this case:
https://sysdig.com/blog/sysdig-2019-container-usage-report/
"Many containers need to only live long enough to execute a function and then terminate when it’s complete. Seconds may seem short, but for some processes, it’s all that is required. We believe the increased use of Kubernetes Jobs that run finite tasks like batch jobs contributed to this growth. In fact, we expect short lifespans to increase, especially on serverless platforms that are well-suited to running short term tasks."
I get the idea of “continuous deployment”, but this is sounding like restart-per-commit. At that point I question how much qualification and validation is happening. No one say unit tests, they aren’t sufficient, and I’ve worked on multiple projects where the pre-commit test suite runs alone can take more than an hour. Even with those tests there are semi-regular breakages.
That's really hard to believe claim with the request throughput you have.