There are a few number of tools out there (Excelsior JET, etc.) that claim to transform Java app's into native executables (*.exe). However it is my understanding that these tools are really just creating native wrappers that invoke/execute java from a shell or command-line.

If that understanding is incorrect, I don't see how it could be. If a running JVM (java process) is essentially a high performance interpreter, loading bytecode from Java classfiles on the fly, then I don't see how a Java app (a collection of bytecode files that serve as input to a JVM) could ever be truly converted into an executable.

This is because the JVM process is already a native executable that takes sets of bytecode files as input. To merge those bytecode files and the JVM process into a single, unified native executable doesn't seem possible without completely rewriting the JVM and de-railing from the JVM specification.

So I ask: how do these tools actually "transform" Java class files into a native executable, or do they?

up vote 24 down vote accepted

All programs have a runtime environment. We tend to forget this, but its there. Standard lib for C that wraps system calls to the operating system. Objective-C has its runtime that wraps all of its message passing.

With Java, the runtime is the JVM. Most of the Java implementations that people are familiar with are similar to the HotSpot JVM which is a byte code interpreter and JIT compiler.

This doesn't have to be the only implementation. There is absolutely nothing saying you can't build a standard lib-esque runtime for Java and compile the code to native machine code and run that within the runtime that handles calls for new objects into mallocs and file access into system calls on the machine. And thats what the Ahead Of Time (AOT rather than JIT) compiler does. Call that runtime what you will... you could call it a JVM implementation (and it does follow the JVM specification) or a runtime environment or standard lib for Java. Its there and it does essentially the same thing.

It could be done either by reimplementing javac to target the native machine (that's kind of what GCJ did). Or it could be done with translating the byte code generated by javac into machine (or byte) code for another machine - that's what Android does. Based on Wikipedia that's what Excelsior JET does too ("The compiler transforms the portable Java byte code into optimized executables for the desired hardware and operating system (OS)"), and the same is true for RoboVM.

There are additional complications with Java that means this is very hard to do as an exclusive approach. Dynamic loading of classes (class.forName()) or proxied objects require dynamics that AOT compilers do not easily provide and so their respective JVMs must also include either a JIT compiler (Excelsior JET) or an interpreter (GCJ) to handle classes that couldn't be precompiled into native.

Remember, the JVM is a specification, with many implementations. The C standard library is also a specification with many different implementations.

With Java8, a fair bit of work has been done on AOT compilation. At best, one can only summarize AOT in general within the confines of textbox. However, in the JVM Language Summit for 2015 (August of 2015), there was a presentation: Java Goes AOT (youtube video). This video is 40 minutes long and goes into many of the deeper technical aspects and performance benchmarks.

  • Sorry, I don't know much about this, but does this mean that java is native now? Or does it mean that there's a new compiler flag that allows us to compile java programs to native code if we want, and we still have the option to compile to byte code, as well? – Pavel Jan 6 '16 at 19:03
  • @paulpaul1076 I would suggest watching the video that I linked. There is quite a bit more in it than I can reasonably fit into a comment. – user40980 Jan 6 '16 at 19:07

gcj minimal runnable example

You can also observe an open source implementation like gcj (now obsolete). E.g. Java file:

public class Main {
    public static void main(String args[]) {
        System.out.println("hello world");
    }
}

Then compile and run with:

gcj -c Main.java
gcj --main=Main -o Main Main.o
./Main

Now you are free to decompile it and see how it works.

file Main.o says it is an elf file.

readelf -d Main | grep NEEDED says it depends on the dynamic libraries:

0x0000000000000001 (NEEDED)             Shared library: [libgcj.so.14]
0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]

So libgcj.so must be where Java functionality is implemented.

You can then decompile it up with:

objdump -Cdr Main.o

and see exactly how it is implemented.

Looks much like C++, lots of name mangling and indirect polymorphic function calls.

I wonder how the garbage collection kicks in. It would be worth looking into: https://stackoverflow.com/questions/7100776/garbage-collection-implementation-in-compiled-languages and other compiled languages with GC like Go.

Tested on Ubuntu 14.04, GCC 4.8.4.

Also take a look at https://en.wikipedia.org/wiki/Android_Runtime , the backbone of Android 5 onwards, which does full AOT to optimize Android apps.

Your Answer

 
discard

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.