back

by wiradikusuma·1y ago·view on hn ↗
"Almost every Java program, library, and framework uses some degree of reflection and dynamic classloading, and so you do have to spend the effort to configure Graal appropriately." -- this is by far the biggest problem with native binary compilation.

Imagine spending 15+ minutes building an exe, and it stopping at minute 15 because some things needed to be added in the config. Or, after waiting 30 minutes to have the exe ready, it refused to run because of the same problem.

The issue is we don't know what we don't know. You don't "forget to include something" because you don't know what to include (and even after seeing the error, you still don't lol).

I just wished all 3rd party libraries put their "include this config to include my lib in your exe", just like OSGi manifest (https://www.ibm.com/docs/en/wasdtfe?topic=overview-osgi-bund...).

For example, an issue still open for almost 2 years: https://github.com/firebase/firebase-admin-java/issues/800

7 comments
This has been a solved problem since Exclesior JET in the 2000's, via agents and PGO, nowadays sadly gone because they no longer can compete with free beer GrallVM and OpenJ9.

https://en.wikipedia.org/wiki/Excelsior_JET

Also ART takes the best of both worlds, as they quickly learned the best is to have JIT/AOT compiler collaborating, thus AOT gets all the reflection related info from the JIT, or Play Store Baseline Profiles and has all it needs to produce a proper AOT binary with the relevant execution flows.

To note that Java and .NET to some extent are catching up to Eiffel, Oberon, Smalltalk and Common Lisp toolchains, of a mix JIT/AOT, while inovating on their own ways as well.

I think https://openjdk.org/jeps/8335368 and https://openjdk.org/jeps/8325147 are two recent JEPs that try to introduce something similar to Google's baseline profiles
Yes, and catch up to OpenJ9 features as well.

https://eclipse.dev/openj9/docs/shrc/#aot-code-and-jit-data

I think you misunderstand the free beer quote because those tools are free as in freedom not as free beer.

Competing looks like starting with what other people are already doing and making it better.

The free beer angle is more relevant because obviously their previous users were OK with paying for non-libre software.
GraalVM also has an agent for Gradle and Maven which can do this.
It is just a normal java agent and works with any jvm app. As in "java -agentlib:... -jar app.jar"

But you do need to start/run your app once to get the agent to do its stuff.

https://www.graalvm.org/latest/reference-manual/native-image...

(it is part of the graalvm so use that as your jvm to do this)

Indeed.
We've been using it at work for a few years. A cli tool, builds in about 6 minutes. We compile it to Linux, Mac ARM and Mac Intel. You're correct about configuring libraries but I found those to be the minority. Most work without configuration. I do this because I will not use Golang if given a choice and Rust is not allowed.
Builds have been slow for a long time via Native Image, but make sure you upgrade to the latest because it has gotten much faster lately. They've also worked hard on binary size.

A pure Java hello world now compiles in a few seconds on a commodity machine and weighs in at about 9mb.

Also, make sure you enable `-Ob` on newer versions of GraalVM, which significantly speeds up build times.

I would only use -Ob for development, it's not for production builds.
How do you compile to multiple platforms? Do you have separate machines for each platform?

Do you use any cli library in front? Is 12-20mb acceptable for a cli executable?

Working on a cli tool poc for work here, hence the interest.

Like the other poster, we have to compile it separately on different architecture machines (we are on Bitbucket). We use Quarkus as the base and picocli as the cli https://quarkus.io/guides/picocli. Quarkus takes care of a lot and makes the native image experience nicer. Size wise, for internal use our users don't complain, since we are all devs.

I think you can shrink the size with one of the optimisation levels https://www.graalvm.org/latest/reference-manual/native-image...

Sorry, I know you weren't asking me, but for this same use case, yes, I've used a GHA build matrix with each OS/arch pair.

Cosmo/APE support would fix this, and GraalVM already ships with a Musl libc implementation, so it isn't very far off.

https://github.com/oracle/graal/issues/8350

I am on the same boat, JVM, .NET and nodejs ecosystems before Go.

So many languages, tools and libraries to chose from, without the "simple minds" culture, even C manages to be more feature rich.

What's your issue with Golang? (Not hating, just curious)
(sorry it's probably it an unpopular opinion) the error handling is hard to read, they purposely didn't incorporate any syntax sugars and innovations of previous years. They were one of the first popular languages with go routines although Project Loom in Java will soon have preemptive multi threading as well.
I guess Quarkus and Spring 3 native are meant for bringing existing apps to serverless deployments and fast startup times (plus process-level isolation as an option?), but it's true there are also new apps being developed using Quarkus. The investment in tooling, inertia of the ecosystem of third-party libs, and predominance of traditional JIT over AOT deployments must be the reason why they're not just doing away with Spring's reflection in the first place.
Micronaut is essentially Spring anyway, but with build-time codegen instead of classpath scanning and runtime annotation processing.

It even has a compat layer, and like Quarkus it supports GraalVM out of the box, with minimal reflection at runtime.

https://micronaut-projects.github.io/micronaut-spring/5.9.0/...

I've built several apps that compile to binary across multiple platforms. The ecosystem is quite mature now, especially if you start from scratch and use things like Quarkus, Helidon or Springboot 3.x. There are *very few* libraries that might need special config.
https://github.com/java-native-access/jna/pull/1608

Much of this configuration could be automatic if more attention were given to GraalVM by library authors.

""Almost every Java program, library, and framework uses some degree of reflection and dynamic classloading" - The .NET framework faced the same issues, and much of their ahead-of-time compilation efforts were removing all those API calls to reflection/introspection calls from the base class library. Years of effort and it is still not 100% there yet - I wouldn't count that this is a lot easier in Javaland.
So springboot and injection does a lot of this; searching the list of known classes for a suitable candidate for injection.

But it’s dynamic by implementation only; to alleviate what came before which was a tedious manual enumeration in xml of those same discoveries.

Presumably we could ask springboot’s discovery to output exactly those finding and the remove the introspection.

But to be clear, tedious manual XML configuration was also Spring. Others may have done it earlier, but Spring still decided it was the way to go, put their name on it, and released it.
You have described Micronaut