Hacker News new | ask | show | jobs
by BuckRogers 3553 days ago
Why was Java ever JIT'd rather than natively compiled anyway? I hate to stick my neck out and even ask this but I never understood why you'd want to JIT or interpret when you can just natively compile to a binary. It seems like Go has gone "back" to the future on this one and in general their toolchain approach to me looked like the way.

I always got the sense the world is waiting for a statically typed Python that compiles to native code with Go's CPU performance. I suppose Nim might fit that bill but a shame it doesn't have compatibility with Python's or even the extent of a language like Go's libraries. And if possible, an imperative language that interfaces with OTP.

And that said, I can see why Erlang/Elixir wouldn't make as much sense or even work with native code AOT compilation due to it's feature set (thinking stuff like hot code reloading). But I've never grasped why Java or Python were better off with JIT or interpreters than AOT comp. Seems like a type system such as Go's is simple enough and allows for good gains in both CPU performance and memory usage. Add in the fact you don't need to install anything and less to think about in deploying and it seems to be a no brainer. Please feel free to fill me in on this or where I went wrong..

5 comments

For several of Java's early use cases, being able to deploy a single file that could be run on any Java-supported platform was very important.

https://en.wikipedia.org/wiki/Write_once,_run_anywhere

Also, a small file and stack based bytecode is often the most compact representation.
This is true and small memory sizes and limited bandwidth were design assumptions valid in 1992.
Because Java was originally designed for set-top TV boxes and appliances, where it's kind of a big deal that you don't need to know or care what OS or processor each appliance is using internally.

https://web.archive.org/web/20050420081440/http://java.sun.c...

When the appliance market didn't pan out, they went for web browsers and Java applets. Bytecodes were a feature because browsers didn't exectute native code, and because it allowed for sandboxing to limit the attack surface.

Even when Java became more popular on the server than in the browser, the "write once, run everywhere" was considered a major feature: The same bytecode could be distributed everywhere; no need to maintain a heap of different build environments for different CPU architecture and OS combinations.

I'd say the appliance market did pan out actually. BluRay players all contain an embedded JVM, as do many other kinds of set top box, as do of course all Android smart TVs.

Abstracting the CPU has worked out pretty well for the Java platform. Look at how easy the 64 bit transition was for the Java world vs the C++ world. Visual Studio is still not a 64 bit app and yet Java IDEs hardly even noticed the change. The transition on Linux was just a disaster zone, every distro came up with their own way of handling the incompatible flavours of each binary.

In addition, a simple JIT compiled instruction set makes on the fly code generation a lot easier in many cases and it's a common feature of Java frameworks. For instance the java.lang.reflect.Proxy feature is one I was using just the other day and it works by generating and loading bytecode at runtime. On the fly code generation is considered a black art for native apps and certainly extremely non portable, but is relatively commonplace and approachable in Java.

Plenty of other platforms do support bytecodes, JIT and AOT on the same toolchain.

So they could keep the WORA story and still offer AOT as an option, which actually most commercial JDKs do.

Just Sun was against providing it at all on Java SE, but they actually supported it on Java Embedded.

I was commenting on "why did they design Java that way in the first place", as opposed to (say) Go.

I agree that once the primary use of Java moved outside the browser, there was no particular reason to not give the option of AOT too. I'm not sure why Sun was so adamantly opposed to the idea.

If I recall correctly, Sun really wanted to stick with JIT on Java Embedded too, they just couldn't get it to run fast enough on embedded hardware. For desktop and servers, they considered bytecode interpretation and JIT "fast enough".

Sure and actually that is where mobile OSes are moving.

We now have bitcode on iDevices, DEX on Android and MSIL/MDIL on WinRT.

Still, both iDevices and Windows Store take, what I consider the best approach, to do AOT on the store for each supported target.

As Google found out, using AOT on the device doesn't scale. I just don't get why they went back to an overly complicated architecture of Interpreter/JIT/PGO → AOT, instead of following the same path as the competition and serve freshly baked AOT binaries.

Religion.

Talking about AOT compilation at Sun was tabu and I remember seeing a few forum discussions from former employees disclosing this.

Plenty of other platforms do support bytecodes, JIT and AOT on the same toolchain.

So they could keep the WORA story and still offer AOT as an option, which actually most commercial JDKs do.

Deployment (and fever dreams of 'mobile code'). It's also worth remembering that Java was designed and implemented at a time when the landscape was significantly less x86-centric and Sun was one of the companies on the not-x86 side.
The landscape is still not really x86 centric is it.

Java is old. It's seen a lot of CPU architectures come and go over the years. When it started out x86, SPARC and POWER, were important. Then it saw a mass migration from x86 to amd64 on the desktop and server side, and an explosion in the importance of ARM in mobiles (several flavours).

Along the way it's seen lots of smaller proprietary architectures come and go too, like the exotic DSP-oriented processors found in BluRay players and pre-smartphone phones and like the Azul Vega architecture that was specifically designed for executing business Java.

And don't forget that even amd64 is not a homogenous architecture. It adds new CPU instructions pretty regularly and thus can be seen as a long line of compatible but different CPU architectures. Java apps transparently get support for all of them on the fly, without having to recompile the world. You see the benefit when you realise the size of Maven Central ... there are JARs out there that are still useful and good even a decade after they were compiled, yet they still get optimised to full speed using the latest CPU instructions no matter what kind of computer you use.

I remember that HotSpot was promising faster than GCC -O2. This sort of over promising was good for presentations to the Schmidt and McNealy types.
My understanding is that GCC has a more diverse arsenal of optimizations that it can apply to code while hotspot has the advantage that it can profile at runtime and apply speculative optimizations based on those profiles and bail out later if things change. In principle it can even optimize code that never reaches steady state as long as the transient states last long enough.

What costs java performance these days is not the quality of the JIT compilers or even the garbage collectors. It's the object layout that is not very cache-friendly. There is lots of pointer-chasing going on since there are no arrays-of-structs.

Valhalla[0] promises to improve the data layout issue at some point in the future while graal may allow compiler writers to cram some more optimizations into the jits.

[0] http://openjdk.java.net/projects/valhalla/

Indeed. Another issue is that java semantics is too rigidly defined. Compilers of many other languages have a lot more room in deciding order of evaluation, vectorization etc
Java tries to provide semi-sane behavior even in the presence of data races. Carefully constructed code can give you benign behavior even under racy data accesses.

If you relaxed some compiler constraints, e.g. allowed it to re-read local variables because they were discarded due to register pressure then those benign races would suddenly turn into non-benign ones.