Skip to content

Opt-in way to start the patched server on the app class path, so JDK 25's AOT cache can hold it #75

Description

@u9g

What

Paperclip starts the server from a URLClassLoader over the patched jars (Paperclip.main). JDK 25's AOT cache (JEP 483, JEP 514) only caches classes loaded by the JVM's built-in loaders. So a cache recorded through Paperclip holds none of Minecraft's or Paper's classes. The training run skipped all 12,520 of them:

[warning][aot] Skipping net/minecraft/world/level/block/TntBlock: Unsupported location
... (12,520 lines)

Started from the same patched jars on the app class path, the same cache cuts startup by about a third.

Numbers

Paper 26.2-124, Temurin 25.0.4+7, -Xms1G -Xmx1G, a fresh world each start (level-seed=1), on an 8-core Linux box. Time from java to Done (:

Start Time to Done
java -jar paper.jar (Paperclip), as today 13.3s
app class path, no cache 12.1s
app class path, -XX:AOTCache=paper.aot 7.9–8.7s

The cache was recorded with -XX:AOTCacheOutput=paper.aot on one start that ran to Done and was then stopped. It's about 195MB.

How we got Paper onto the app class path

We ran -Dpaperclip.patchonly=true into a bundlerRepoDir. Next to it we put a manifest-only jar, Main-Class: org.bukkit.craftbukkit.Main, whose Class-Path lists versions/… and then libraries/…, in the order META-INF/versions.list and META-INF/libraries.list give. The paths are relative to the jar, so the folder can move. Then:

java -XX:AOTCache=paper.aot -jar <repo>/launch.jar --nogui

Our server images now start this way, with a plugin loaded, and pass our start-up checks. The class-path order is the same as Paperclip's, versions first, as setupClasspath requires.

Two things we learned that a user would need to know:

  • The JVM checks every class-path jar's size and modification time against the cache. If anything differs, for example after a repatch, it prints [error][aot] … timestamp has changed and starts without the cache. So a stale cache fails safe, just slower.
  • A cache recorded with the JDK and the jars at other paths still works when they're moved, as long as their relative layout and times are the same. We record ours at image build time.

Proposal

An opt-in, leaving the default launch alone. For example, while patching Paperclip could also write launch.jar (as above) into the bundlerRepoDir, perhaps only when asked with a system property. Anyone wanting the AOT cache would then run that jar instead of paper.jar.

This keeps what #35 was after: the server loaded as if started with plain java -jar, on the JVM's own loader. It would also sidestep the service-loader problem in #66, since the app loader is the parent there.

If a Paperclip-side change isn't wanted, a short note in the readme on doing this by hand with paperclip.patchonly would still help people on Java 25.

I'm happy to open a PR for whichever shape you prefer.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions