Leyden has been relying pretty heavily on training runs which I get, but man is that a pretty hard burden to setup.
The tooling for doing these sorts of training runs doesn't really exist, you end up needing to do something more bespoke as part of your build pipeline if you want it there. That can be especially tricky with more complicated applications like the ones I maintain.
I like what these can deliver, but dislike the effort needed to get it going.
The complexity of training depends heavily on your goals. If your goal is to improve startup time, it's actually pretty simple.
If your goal is peak performance across the board and you can't wait for runtime optimizations to kick in, you're going to have a harder time. But in many of the things I've worked on, startup time has been a bigger issue than JIT->peak.
I completely agree. I know that there has been some work on command line ergonomics for this stuff, but my hope is that after the bulk of the Leyden work is done, another project starts up aimed at unifying all of the various disparate tools that modern Java now has.
You have training runs to generate bytecode/compiled code, you have jlink to create a distributable JRE for your app, leaning on jdeps and various other tools to correctly create it, you have the venerable jar file, but all in all the process of actually getting a java project ready to distribute in a way that doesn't presuppose a relevant pre-installed JRE on the user's side, and which takes advantage of the various facilities available to modern Java is still a pretty bespoke and painful process.
Compared to Go and Rust, and just throwing a static binary to someone or some server, Java kind of sucks. And that's unfortunate because the language, the runtime, and even the dependency situation all feels pretty competent, there's not some huge gulf that makes Java "old". Its really just the every about setting up a project and then distributing it, so the "start" and "end" of a project, that currently sucks, and I fear that so much of the great work the Java team is doing is going to go largely unused if they don't work to make it easier for the average developer to utilize it.
I have a company that makes Conveyor, which is very good at distributing JVM apps. It's primarily meant for desktop apps and replaces jpackage with something that can cross-build, make apps self-updating, auto-minimize the bundled JDK and so on.
Adapting it for servers is something I think about sometimes, and in fact it does already support that to some extent, but it produces debs for that use case.
It's just sort of unclear what JVM developers lack here that isn't already solved by build tool plugins and Docker. Or are you thinking about CLI tooling? Because I have something cooking for that which might be pretty nice.
My ideal tool would be less tooling, which I know is not a great answer. But what I mean by this is that I can probably cobble together a build pipeline that handles most of this stuff if I spend enough time working with Gradle. And the reality of Java's popularity in the enterprise means that its build tools generally orient around builds that can be crazy and bespoke and so immense flexibility is usually needed. I totally get that need, and I'm not begrudging any of that work.
I'm speaking more from my view as just an individual Joe Sixpack developer. If I'm not developing an enterprise behemoth, and let's just say I want to whip up a command line utility to do some image processing, or network communication, etc, Java's fantastic standard lib and ecosystem means I can do all of that pretty easily. But when I want to distribute that, what does it look like? What comes out the other side of the CI build? I have to integrate jlink with gradle, which is kind of a pain, I have to somehow integrate a training run and then package up the aot cache output and make sure that the directories are handled properly, and what do I ship to the user? A tar/zip file that they have to somehow copy and decompress to the relevant directory, maybe with #! file in .local/bin to get it on the path? By contrast the situation with Go or Rust is immensely easier, even if that actual development process may almost be harder because I have to spend time faffing about deciding which image library to pull in for Go, or dealing with async for Rust. But once that is actually done, I just ship the binary and simply copy it somewhere to get it on the path, and I never have to worry about reading gradle/maven docs or anything like that.
So I'll finish by saying that my ideal build tool is probably not a great one. What it is crucially is a simple one. I think that's that gap right now, that Java has these huge, immensely powerful and general build tools, build tools that I am guessing eventually gain equivalents in the Rust and Go worlds when projects grow old and gnarly enough to require a bunch of custom build stuff, but it doesn't have baby's first build tool like go build and cargo, where you can just get an idea and start building and come out the other side with an artifact and 0 time spent on thinking about the build system, until you actually need the kind of complexity that maven and gradle provide. Give me a build tool that assumes the standard src/ and test/ directory layout, then builds everything into some sort of archive file, creates a minimized JRE to run it, accepts an argument for an aot.cache file location, or for the more difficult option runs the program to generate that file and then bundles it up with the archive, bundles all the libraries that I've pulled into the project up into the archive along with all the resources declared for the project as well, and then spits out a single file artifact that I can immediately use and run. Give me a build tool that I don't even realize is there until I actually need that complexity.
I understand if this is vague and not a great answer, and I also appreciate all the work you've done for the Java community, so thanks for replying.
Yeah, I agree the situation for CLI apps specifically could be a lot better, especially as the complexity keeps going up.
Native Image does produce single binaries in many cases, but what we need here is a much better tool in general. Actually I'm working on one right now, although it's not JVM specific. Want to take a look?
Are you describing jpackage? I'm not sure how you would make it less bespoke, given you are essentially creating a Java runtime for your program. Maybe the Java folks could tree-shake the runtime for you so you wouldn't have to care about their weird module system, but it's not the worst thing to learn.
It would be nice if jpackage could bundle the generated AOT configuration data along with a custom action in the MSI package to run the machine-dependent AOT compilation step at install time.
jpackage is heavily oriented around installable artifacts, it creates a .deb or .rpm for Linux for example. So if I wanted to throw someone a cli utility I can literally just send them the binary if I’m working in Go, whereas Java they need to use dpkg to install the deb. This somewhat complicates building a docker image as well. It’s the same story for windows and Mac as well. If I’m creating a Swing app or something similar this is very convenient but anything else and jpackage isn’t ideal. Not to mention that I don’t know what the integration is currently with jpackage and the aot cache that Leyden produces but I’m guessing it isn’t super straight forward.
I’m sure that all of this stuff can be strung together after spending a suitable amount of time trolling through the documentation of the individual tools, but it’s far cry from go build and cargo build, and I think it would be great if Java could narrow the gap in this aspect.
Comments
Leyden has been relying pretty heavily on training runs which I get, but man is that a pretty hard burden to setup.
The tooling for doing these sorts of training runs doesn't really exist, you end up needing to do something more bespoke as part of your build pipeline if you want it there. That can be especially tricky with more complicated applications like the ones I maintain.
I like what these can deliver, but dislike the effort needed to get it going.
The complexity of training depends heavily on your goals. If your goal is to improve startup time, it's actually pretty simple.
If your goal is peak performance across the board and you can't wait for runtime optimizations to kick in, you're going to have a harder time. But in many of the things I've worked on, startup time has been a bigger issue than JIT->peak.
Yes, OpenJ9 and ART do it much more easily, the JIT cache is updatable across executions, so there isn't an explicit training run required.
I completely agree. I know that there has been some work on command line ergonomics for this stuff, but my hope is that after the bulk of the Leyden work is done, another project starts up aimed at unifying all of the various disparate tools that modern Java now has. You have training runs to generate bytecode/compiled code, you have jlink to create a distributable JRE for your app, leaning on jdeps and various other tools to correctly create it, you have the venerable jar file, but all in all the process of actually getting a java project ready to distribute in a way that doesn't presuppose a relevant pre-installed JRE on the user's side, and which takes advantage of the various facilities available to modern Java is still a pretty bespoke and painful process.
Compared to Go and Rust, and just throwing a static binary to someone or some server, Java kind of sucks. And that's unfortunate because the language, the runtime, and even the dependency situation all feels pretty competent, there's not some huge gulf that makes Java "old". Its really just the every about setting up a project and then distributing it, so the "start" and "end" of a project, that currently sucks, and I fear that so much of the great work the Java team is doing is going to go largely unused if they don't work to make it easier for the average developer to utilize it.
What would be in your ideal tool?
I have a company that makes Conveyor, which is very good at distributing JVM apps. It's primarily meant for desktop apps and replaces jpackage with something that can cross-build, make apps self-updating, auto-minimize the bundled JDK and so on.
Adapting it for servers is something I think about sometimes, and in fact it does already support that to some extent, but it produces debs for that use case.
It's just sort of unclear what JVM developers lack here that isn't already solved by build tool plugins and Docker. Or are you thinking about CLI tooling? Because I have something cooking for that which might be pretty nice.
My ideal tool would be less tooling, which I know is not a great answer. But what I mean by this is that I can probably cobble together a build pipeline that handles most of this stuff if I spend enough time working with Gradle. And the reality of Java's popularity in the enterprise means that its build tools generally orient around builds that can be crazy and bespoke and so immense flexibility is usually needed. I totally get that need, and I'm not begrudging any of that work.
I'm speaking more from my view as just an individual Joe Sixpack developer. If I'm not developing an enterprise behemoth, and let's just say I want to whip up a command line utility to do some image processing, or network communication, etc, Java's fantastic standard lib and ecosystem means I can do all of that pretty easily. But when I want to distribute that, what does it look like? What comes out the other side of the CI build? I have to integrate jlink with gradle, which is kind of a pain, I have to somehow integrate a training run and then package up the aot cache output and make sure that the directories are handled properly, and what do I ship to the user? A tar/zip file that they have to somehow copy and decompress to the relevant directory, maybe with #! file in .local/bin to get it on the path? By contrast the situation with Go or Rust is immensely easier, even if that actual development process may almost be harder because I have to spend time faffing about deciding which image library to pull in for Go, or dealing with async for Rust. But once that is actually done, I just ship the binary and simply copy it somewhere to get it on the path, and I never have to worry about reading gradle/maven docs or anything like that.
So I'll finish by saying that my ideal build tool is probably not a great one. What it is crucially is a simple one. I think that's that gap right now, that Java has these huge, immensely powerful and general build tools, build tools that I am guessing eventually gain equivalents in the Rust and Go worlds when projects grow old and gnarly enough to require a bunch of custom build stuff, but it doesn't have baby's first build tool like go build and cargo, where you can just get an idea and start building and come out the other side with an artifact and 0 time spent on thinking about the build system, until you actually need the kind of complexity that maven and gradle provide. Give me a build tool that assumes the standard src/ and test/ directory layout, then builds everything into some sort of archive file, creates a minimized JRE to run it, accepts an argument for an aot.cache file location, or for the more difficult option runs the program to generate that file and then bundles it up with the archive, bundles all the libraries that I've pulled into the project up into the archive along with all the resources declared for the project as well, and then spits out a single file artifact that I can immediately use and run. Give me a build tool that I don't even realize is there until I actually need that complexity.
I understand if this is vague and not a great answer, and I also appreciate all the work you've done for the Java community, so thanks for replying.
Yeah, I agree the situation for CLI apps specifically could be a lot better, especially as the complexity keeps going up.
Native Image does produce single binaries in many cases, but what we need here is a much better tool in general. Actually I'm working on one right now, although it's not JVM specific. Want to take a look?
Are you describing jpackage? I'm not sure how you would make it less bespoke, given you are essentially creating a Java runtime for your program. Maybe the Java folks could tree-shake the runtime for you so you wouldn't have to care about their weird module system, but it's not the worst thing to learn.
It would be nice if jpackage could bundle the generated AOT configuration data along with a custom action in the MSI package to run the machine-dependent AOT compilation step at install time.
Jpackage or jlink should be extended to support creating AppImages.
jpackage is heavily oriented around installable artifacts, it creates a .deb or .rpm for Linux for example. So if I wanted to throw someone a cli utility I can literally just send them the binary if I’m working in Go, whereas Java they need to use dpkg to install the deb. This somewhat complicates building a docker image as well. It’s the same story for windows and Mac as well. If I’m creating a Swing app or something similar this is very convenient but anything else and jpackage isn’t ideal. Not to mention that I don’t know what the integration is currently with jpackage and the aot cache that Leyden produces but I’m guessing it isn’t super straight forward.
I’m sure that all of this stuff can be strung together after spending a suitable amount of time trolling through the documentation of the individual tools, but it’s far cry from go build and cargo build, and I think it would be great if Java could narrow the gap in this aspect.
One could also just use jlink and extract the zip file that it produces.