Fix slow class loading of relocated app with AppCDS/AOT - #618
Fix slow class loading of relocated app with AppCDS/AOT#618Alexander Maryanovsky (m-sasha) wants to merge 1 commit into
Conversation
|
Should such low-level fix be proposed to OpenJDK for review instead ? |
|
It's not specific to JBR, and we should definitely upstream it. But I think we should have someone from the JBR team review it first. Given that it was implemented by AI, we should make sure it's good first. |
|
"I confirm that I make this contribution in accordance with the OpenJDK Interim AI Policy." No fix can be made by AI... should it be rewritten or ...? |
|
Hehe, I didn't read the text of that snippet; just assumed it was a standard rights-from-contributor thing. |
This PR addresses the main problem reported in JBR-9098 AppCDS writes local paths into .jsa archive, namely the slowdown in application startup.
The PR is the result of an investigation by myself, following a session with Claude Code Opus 4.8, which verified the problem and produced a fix. I verified that the fix works (the slowdown is gone), but other than that I can't vouch for the code.
Below is a write-up by Claude:
Symptom
With an AppCDS (
.jsa) or AOT cache created from an absolute classpath, moving the application directory and launching from the new location loses all of the archive's startup benefit — startup becomes slower than running with no archive at all (on Windows, ~866 ms vs ~390 ms baseline for a 2,000-class app; the never-moved case is ~183 ms). All classes still load from the archive; the time is spread evenly across every class load. A secondary, cross-platform symptom: archived classes in a relocated app get anullCodeSource.Root cause
CDSProtectionDomain::get_shared_jar_url()usesnullas its "not yet computed" sentinel and builds the URL from the raw dump-time classpath path. When the app has moved, that path no longer exists, soClassLoaders.toFileURL()returnsnullafter a fullPath.toRealPath()canonicalization walk of the dead path — and becausenullis never cached, the walk is repeated for every class loaded from that entry. On Windows each walk isFindFirstFileper path component through the filter-driver stack (~30 metadata syscalls/class here), which is what makes it dominate startup; on macOS/Linux the same repeated walk is far cheaper and effectively invisible.Separately,
AOTClassLocationConfig::validate()already detects the relocation and accepts the archive via longest-common-prefix substitution — but then discards the substituted (live) path, so class loading still uses the stale one.Solution
validate()and addAOTClassLocationConfig::runtime_path(index), which returns the dump-time path with the substitution applied — i.e. where the entry actually lives now. Consume it inget_shared_jar_url()and the JVMTI classfile-stream path. This resolves successfully for a moved app, so the URL is computed once and cached, andCodeSourceis correct again._shared_jar_url_computed[]flag so thetoFileURLupcall runs at most once per classpath entry even when it returnsnull(covers cases LCP can't fix, e.g. a relative dump-time path resolved against a different runtime CWD). The now-expectednullreturn is documented; the debug-onlyassert(url_h.not_null())is removed.Relocated-app startup returns to the never-moved figure (866→183 ms jsa, 608→100 ms aot), metadata syscalls drop from ~61k to ~1.1k, and no non-moved scenario regresses. The bug reproduces on stock OpenJDK 25, so this is not JBR-specific.