Skip to content

perf(desktop): ship a class data sharing archive with desktop app images - #421

Merged
linroid merged 3 commits into
mainfrom
perf/desktop-appcds
Oct 7, 2026
Merged

linroid merged 3 commits into
mainfrom
perf/desktop-appcds

Conversation

@linroid

@linroid linroid commented Oct 7, 2026

Copy link
Copy Markdown
Owner

What

Desktop app images now ship an AppCDS archive, ketch.jsa, beside their jars. The JVM maps the classes a launch needs from it instead of parsing them again. Its read-only part stays clean memory the system shares.

  • createDistributable and createReleaseDistributable dump it in a doLast, through the image's own launcher, since an archive only works with the JVM that wrote it. Packages and the portable zip pick it up from the image.
  • The classes come from app/desktop/cds/app.classlist, recorded from an unobfuscated build. For release images they're renamed through ProGuard's mapping.txt (99% of entries survive). ./gradlew :app:desktop:recordCdsClassList records the list again; classes it misses still load from the jars.
  • The launcher passes -XX:SharedArchiveFile=$APPDIR/ketch.jsa -XX:+UnlockDiagnosticVMOptions -XX:ArchiveRelocationMode=0. JDK 21 maps the archive at a random address by default, which rewrites most of it and erased most of the saving in measurements. On macOS the archive is dumped for 0x7000000000, because the JVM's default address isn't free there.
  • Adding the file breaks jpackage's ad-hoc seal on macOS bundles, so the bundle is signed again the same way, keeping its hardened runtime and entitlements.
  • A JVM that finds no archive, as with ./gradlew run, loads classes as before.

Results

Release (ProGuard) app, Apple Silicon, physical footprint:

Without With AppCDS
In the tray / --background 167 MB 117–119 MB
Window shown 575–583 MB 512–513 MB
Launch → window on screen 1.5–1.6 s 1.0–1.3 s
DMG (ULMO, as the release workflow converts it) 48.6 MB 63.1 MB
Installed app 122 MB 191 MB

Before merging

  • Only macOS arm64 has run this. Windows, Linux and Intel Macs dump through their launchers (Ketch.exe, bin/Ketch) for the first time in the release workflow, the only place desktop packages are built. Please push an rc tag from this branch first. A failed dump fails the build, so a broken platform shows up there rather than shipping without the archive.
  • -XX:ArchiveRelocationMode=0 is a diagnostic flag, and it means the archive's class metadata is not address-randomized, as was the default up to JDK 19. Revisit it when the bundled JDK moves past 21.
  • If macOS packages are ever signed with a Developer ID, the archive has to be added before signing. The build fails with that message if it finds an identity signature.

Testing

  • Release image: 69.6 MB archive; codesign --verify --strict passes with the original adhoc,runtime flags and entitlements.
  • Launched from the mounted DMG (a different path than it was dumped at): 10,183 classes from the archive and 1 from the jars; mapped at its requested address.
  • The archive still applies after moving the bundle and after changing the jars' timestamps.
  • A missing archive is silent; the configuration cache is reused across builds.
  • recordCdsClassList runs the app against an isolated profile under build/cds/profile and writes the list once the app is quit.
  • ./gradlew :app:desktop:test passes.

Independent of #420; the two merge cleanly in either order.

Every launch parsed the same ~12,000 classes into about 100 MB of
metaspace and symbols. createDistributable and createReleaseDistributable
now dump an AppCDS archive, ketch.jsa, into the app image, and the
launcher maps the classes from it; its read-only part stays clean memory
the system shares.

- The image's own launcher dumps the archive, since one only works with
  the JVM that wrote it, from app/desktop/cds/app.classlist. For release
  images the list gets ProGuard's names from mapping.txt, which kept 99%
  of it. recordCdsClassList records the list again.
- JDK 21 maps the archive at a random address by default, which moves
  every pointer in it and leaves little shared, so the launcher passes
  -XX:ArchiveRelocationMode=0 (diagnostic) and macOS archives are dumped
  for 0x7000000000, as the JVM's default address isn't free there.
- Adding a file breaks the ad-hoc seal jpackage puts on macOS bundles,
  so the bundle is signed again the same way, keeping its hardened
  runtime and entitlements.
- A JVM that finds no archive, as in ./gradlew run, loads classes as
  before.

Release app on Apple Silicon: 167 -> 117 MB in the background,
575-583 -> 512-513 MB with the window shown, and the window in 1.0-1.3 s
instead of about 1.5 s. It adds about 70 MB to the installed app and
15 MB to the DMG.
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-07T02:55:16.275554Z e38a581 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e38a58137c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread app/desktop/build.gradle.kts
@github-actions

github-actions Bot commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Test Results

 1 392 files  ±0   1 419 suites  ±0   10m 11s ⏱️ +7s
 9 144 tests ±0   8 915 ✅ ±0  229 💤 ±0  0 ❌ ±0 
10 483 runs  ±0  10 254 ✅ ±0  229 💤 ±0  0 ❌ ±0 

Results for commit 64ecb51. ± Comparison against base commit e50759c.

♻️ This comment has been updated with latest results.

@linroid
linroid merged commit b164588 into main Oct 7, 2026
13 checks passed
@linroid
linroid deleted the perf/desktop-appcds branch October 7, 2026 03:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant