But the thing is that Java is pretty close to native. Primitive types, object allocation, threads etc. all correlate almost directly to how CPUs and OSs work. So you get a sense of performance and what's expensive/isn't.
With JavaScript there are so many complex abstractions and constructs it's really hard to get a sense of what the final ASM will look like in an optimal case. Unfortunately, some Java changes (e.g. Valhalla) aim to bring some of that lack of clarity into Java. Still, thanks to typing and a simpler syntax Java is MUCH better positioned here than JavaScript.
I'd like to add that this argument exists for pretty much all "slow" languages and that "if you're careful" often can mean "throw away all upsides your language provides" [1].
Personally, I find these discussions tiring because the ones arguing such points are usually the kind of person that doggedly clings to the "wrong tool for the job".
For example: Although I see rust as the better alternative in many places, I personally find C++ to still be a better language than Java. That doesn't mean I'll insist that Java has no worth or that every program should be written in lower level languages. Horses for courses.
Why can't we just go the Python way and accept the shortcomings of the languages we hold dear?
[1]: Edit: For GCed languages, this often is "avoid the GC at all costs and essentially write less readable C".
That doesn’t solve the problem of course. I have this metric that I use to philosophize about this: SIPS, or static instructions per second. If your program has low SIPS (I.e. it’s the benchmark of HotSpot’s wet dreams or OP’s post) it means that the total code is small and it runs a long time - so the JIT will go on a rampage early on and then you’re good. If the program has high SIPS, then the JIT will still be catching up when the program terminates. Most real things that aren’t web servers have low SIPS.
I haven't really paid attention to V8's policies for years, but generally speaking, it still does this to some extent.
I think of SIPS as basically the resident set size for code. Depending on how the system ages your code, you might be stuck with unused JIT code until it can be recycled, if at all. V8 used to have code aging and the implementation was a huge bug farm. I am not sure if that survived the switch to TurboFan/Ignition. Of course, V8 GC's code about as aggressively as it compacts the old generation.
Other than that JSC has only very weak heuristics for aging out code. And on sufficiently empowered devices our JIT pool is hundreds of megs.
So, we will happily compile your whole program with the jit if your whole program is warm and runs long enough.
JavaScript, Java, C#, generally all JITed languages will eventually end up exhausting the size of their JIT code caches. You just gotta hope that your hot paths are contained in there and that the JIT balances its hunger for deep inlining with the need to not overcompile. Because there's nothing worse for a JIT when the entire application is just a tepid soup and there are no hotspots. Then you aren't getting escape analysis, you end up with tons of polymorphism, and generally, performance suffers.
Despite 20 years working on JITs and dynamic optimization, I am more convinced than ever you just can't beat static compilation and programs designed to not over-allocate, to not overabstract with too much polymorphism, closures, and heavy allocation. Programs still need to be a bit miserly to get maximum performance.