RFR: 8311302: Implement JEP 493: Linking Run-Time Images without JMODs [v40]
Mandy Chung
mchung at openjdk.org
Fri Oct 25 20:02:29 UTC 2024
On Fri, 25 Oct 2024 16:29:52 GMT, Severin Gehwolf <sgehwolf at openjdk.org> wrote:
>> Please review this patch which adds a jlink mode to the JDK which doesn't need the packaged modules being present. A.k.a run-time image based jlink. Fundamentally this patch adds an option to use `jlink` even though your JDK install might not come with the packaged modules (directory `jmods`). This is particularly useful to further reduce the size of a jlinked runtime. After the removal of the concept of a JRE, a common distribution mechanism is still the full JDK with all modules and packaged modules. However, packaged modules can incur an additional size tax. For example in a container scenario it could be useful to have a base JDK container including all modules, but without also delivering the packaged modules. This comes at a size advantage of `~25%`. Such a base JDK container could then be used to `jlink` application specific runtimes, further reducing the size of the application runtime image (App + JDK runtime; as a single image *or* separate bundles, depending on the app
being modularized).
>>
>> The basic design of this approach is to add a jlink plugin for tracking non-class and non-resource files of a JDK install. I.e. files which aren't present in the jimage (`lib/modules`). This enables producing a `JRTArchive` class which has all the info of what constitutes the final jlinked runtime.
>>
>> Basic usage example:
>>
>> $ diff -u <(./bin/java --list-modules --limit-modules java.se) <(../linux-x86_64-server-release/images/jdk/bin/java --list-modules --limit-modules java.se)
>> $ diff -u <(./bin/java --list-modules --limit-modules jdk.jlink) <(../linux-x86_64-server-release/images/jdk/bin/java --list-modules --limit-modules jdk.jlink)
>> $ ls ../linux-x86_64-server-release/images/jdk/jmods
>> java.base.jmod java.net.http.jmod java.sql.rowset.jmod jdk.crypto.ec.jmod jdk.internal.opt.jmod jdk.jdi.jmod jdk.management.agent.jmod jdk.security.auth.jmod
>> java.compiler.jmod java.prefs.jmod java.transaction.xa.jmod jdk.dynalink.jmod jdk.internal.vm.ci.jmod jdk.jdwp.agent.jmod jdk.management.jfr.jmod jdk.security.jgss.jmod
>> java.datatransfer.jmod java.rmi.jmod java.xml.crypto.jmod jdk.editpad.jmod jdk.internal.vm.compiler.jmod jdk.jfr.jmod jdk.management.jmod jdk.unsupported.desktop.jmod
>> java.desktop.jmod java.scripting.jmod java.xml.jmod jdk.hotspot.agent.jmod jdk.i...
>
> Severin Gehwolf has updated the pull request incrementally with five additional commits since the last revision:
>
> - Better handle patched modules
>
> Also add a test which ensures that module patching (if present), will
> get an appropriate error message.
> - Add /othervm to some langtools tier1 tests
>
> Those tests are using module patches from JTREG. Since the run-time
> image based link uses ModuleFinder.ofSystem(), which will see the extra
> classes comming from the module patch. Then the size look-up using the
> JRT FS provider fails, since that only looks in the module image
> (lib/modules) and NOT also in the patch. Thus, we get a
> NoSuchFileException and the link fails.
>
> Run the tests with othervm so that the JTREG patch'ed module isn't
> visible to the test.
> - Fix tests for builds with --enable-linable-runtime
>
> Those builds don't include the packaged modules, `jmods` directory.
> However, some tests assume that they're there. Add appropriate requires
> tag.
> - Fix provider verification when some JMODs are present
>
> In some configurations, e.g. when java.base is missing from the packaged
> modules, but another JDK module is present as JMOD that is linked into
> an image, then provider verification can fail. Thus, the run-time image
> link fails. Verify that this doesn't happen.
>
> The fix is to return Platform.runtime() for run-time image based links
> as well. Otherwise this code would return the wrong result.
> - Show run-time image based capability in help
>
> Also add a test for it when it's turned on and off.
I did a pass of the current implementation (not the tests). Looking quite good.
I see you add the last line in the help message.
Capabilities: +run-time-image
This needs some discussion/consideration how that information be conveyed.
src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImageFileCreator.java line 111:
> 109: * @throws IOException
> 110: */
> 111: public static ExecutableImage create(Set<Archive> archives,
I think these `create(Set<Archive>, ImagePluginStack)` and `create(Set<Archive>, ByteOrder)` methods are unused. Can just remove them.
src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JRTArchive.java line 104:
> 102: this.errorOnModifiedFile = errorOnModifiedFile;
> 103: this.otherRes = readModuleResourceFile(module);
> 104: this.resDiff = prepareDiffMap(Objects.requireNonNull(perModDiff));
Suggestion:
this.resDiff = Objects.requireNonNull(perModDiff).stream()
.collect(Collectors.toMap(ResourceDiff::getName, Function.identity()));
src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JRTArchive.java line 183:
> 181: null /* hashOrTarget */,
> 182: false /* symlink */,
> 183: resDiff.get(lookupKey));
Nit: identation within `{...}` better to use consistent spacing (4-space or 8-space is fine).
src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JRTArchive.java line 197:
> 195: String pathWithoutModule = s.getName()
> 196: .substring(secondSlash + 1,
> 197: s.getName().length());
Suggestion:
String pathWithoutModule = s.getName().substring(secondSlash + 1);
src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JRTArchive.java line 205:
> 203: s);
> 204: })
> 205: .collect(Collectors.toList()));
Suggestion:
.toList());
Can replace `.collect(Collectors.toList())` with `Stream::toList` in other occurrances as well.
src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JRTArchive.java line 326:
> 324: try {
> 325: Integer typeInt = Integer.valueOf(tokens[0]);
> 326: type = Type.fromOrdinal(typeInt);
Since encoding and decoding are private to this class, this class can keep a map from an ordinal to `Type` and no need to add the static `Type::fromOrdinal` method.
private static final Map<Integer, Type> typeMap = Arrays.stream(Type.values())
.collect(Collectors.toMap(Type::ordinal, Function.identity()));
Suggestion:
type = typeMap.get(typeInt);
src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JRTArchive.java line 526:
> 524: }
> 525: } catch (IOException e) {
> 526: throw new InternalError("Failed to process run-time image resources " +
For unexpected exceptions, some code throws InternalError and some throws AssertionError - better to be consistent and I think implementation typically throws InternalError. For IOException, I think `UncheckedIOException` is appropriate.
src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java line 246:
> 244: public static final String RESPATH_PATTERN = "jdk/tools/jlink/internal/runtimelink/fs_%s_files";
> 245: // The diff files per module for linkable JDK runtimes
> 246: public static final String DIFF_PATTERN = "jdk/tools/jlink/internal/runtimelink/diff_%s";
I think it'd be useful to have a `LinkableRuntimeImage` class that declares these constants and some other utility methods such as detecting if this is a linkable runtime image (e.g. `hasRuntimeImageLinkCap()`)
src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java line 414:
> 412: }
> 413: // Setup and init actions for JDK linkable runtimes
> 414: LinkableRuntimesResult result = linkableJDKRuntimesInit(finder, roots);
I think it's clearer to the readers if inlining the code of `linkablejDKRuntimesInit` here. Also the `--keep-package-modules` check can be moved after `isLinkFromRuntime` is determined so checking `isLinkFromRuntime` is more explicit.
Suggestion:
boolean isLinkFromRuntime = options.modulePath.isEmpty();
// In case of custom modules outside the JDK we may
// have a non-empty module path, which must not include
// java.base. If it did, we link using packaged modules from that
// module path. If the module path does not include java.base, we must
// have a linkable JDK runtime. In that case we take the JDK modules
// from the run-time image.
if (finder.find("java.base").isEmpty()) {
isLinkFromRuntime = true;
ModuleFinder runtimeImageFinder = ModuleFinder.ofSystem();
finder = combinedFinders(runtimeImageFinder, finder, options.limitMods, roots);
}
// --keep-packaged-modules doesn't make sense as we are not linking
// from packaged modules to begin with.
if (isLinkFromRuntime && options.packagedModulesPath != null) {
throw taskHelper.newBadArgs("err.runtime.link.packaged.mods");
}
src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java line 480:
> 478: */
> 479: private ModuleFinder combinedFinders(ModuleFinder finder,
> 480: ModuleFinder runtimeImageFinder, Set<String> limitMods,
suggest to make `runtimeImageFinder` as the first parameter and `finder` second to match the order of the lookup.
src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java line 486:
> 484: @Override
> 485: public Optional<ModuleReference> find(String name) {
> 486: Optional<ModuleReference> basic = runtimeImageFinder.find(name);
Suggestion:
Optional<ModuleReference> mref = runtimeImageFinder.find(name);
src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java line 559:
> 557: Runtime.Version version = Runtime.version();
> 558: Path[] entries = paths.toArray(new Path[0]);
> 559: ModuleFinder finder = paths.isEmpty() ? ModuleFinder.ofSystem()
The javadoc needs update to something like:
* Returns a module finder of the given module path or system modules
* if the module path is empty that limits the observable modules to ...
src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java line 741:
> 739: // we don't have the per module resource diffs in the modules image
> 740: try (InputStream inStream = getDiffInputStream()) {
> 741: if (inStream == null) {
Suggest to define a method `isLinkableRuntime` in the new `LinkableRuntimeImage` class to test if it's a linkable runtime image.
src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java line 760:
> 758: }
> 759:
> 760: private static boolean hasRuntimeImageLinkCap() {
Move to the new `LinkableRuntimeImage` class
src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java line 825:
> 823: }
> 824: } else if (config.linkFromRuntimeImage()) {
> 825: // This is after all other archive types, since user-provided
This block can be a utility method in the new `LinkableRuntimeImage` class as the logic can be encapsulated.
src/jdk.jlink/share/classes/jdk/tools/jlink/internal/runtimelink/ImageReader.java line 39:
> 37: }
> 38:
> 39: public static boolean isNotTreeInfoResource(String path) {
Do we need this? Can use `ImageResourcesTree::isTreeInfoResource` instead
src/jdk.jlink/share/classes/jdk/tools/jlink/internal/runtimelink/ImageReader.java line 48:
> 46: .filter(ImageReader::isNotTreeInfoResource)
> 47: .sorted()
> 48: .collect(Collectors.toList());
Suggestion:
return Arrays.stream(getEntryNames())
.filter(Predicate.not(ImageResourcesTree::isTreeInfoResource))
.sorted()
.toList();
src/jdk.jlink/share/classes/jdk/tools/jlink/internal/runtimelink/JmodsReader.java line 41:
> 39:
> 40: @SuppressWarnings("try")
> 41: public class JmodsReader implements JimageDiffGenerator.ImageResource {
Is JmodsReader leftover from previous implementation? It can be removed?
src/jdk.jlink/share/classes/jdk/tools/jlink/internal/runtimelink/ResourceDiff.java line 272:
> 270:
> 271: public static void printDiffs(List<ResourceDiff> diffs) {
> 272: for (ResourceDiff diff: diffs.stream().sorted().collect(Collectors.toList())) {
Suggestion:
for (ResourceDiff diff: diffs.stream().sorted().toList()) {
src/jdk.jlink/share/classes/jdk/tools/jlink/internal/runtimelink/RuntimeImageLinkException.java line 29:
> 27:
> 28: /**
> 29: * Exception thrown for links without packaged modules. I.e. run-image link.
I found inconsistent terminologies "run-image" "run-time" "JDK run-time". Better to use consistent terminologies.
For this one, maybe as simple as this:
Suggestion:
* Exception thrown for linking without packaged modules
src/jdk.jlink/share/classes/jdk/tools/jlink/internal/runtimelink/RuntimeImageLinkException.java line 38:
> 36: private final IllegalArgumentException iae;
> 37:
> 38: public RuntimeImageLinkException(IllegalArgumentException cause) {
Can you explain why `RuntimeImageLinkException` constructor takes IAE. The places throwing RILE has to create IAE. I can't see how IAE is necessary as other places would catch RILE and throw IAE or IOException instead.
Can it simply take an exception message parameter? And possibly change `JlinkTask::run` in handling RILE like IAE?
} catch (IllegalArgumentException | ResolutionException | RuntimeImageLinkException e) {
log.println(taskHelper.getMessage("error.prefix") + " " + e.getMessage());
if (DEBUG) {
e.printStackTrace(log);
}
return EXIT_ERROR;
```
src/jdk.jlink/share/classes/jdk/tools/jlink/plugin/ResourcePoolEntry.java line 73:
> 71: TOP;
> 72:
> 73: public static Type fromOrdinal(int value) {
Can be dropped if `JRTArchive.ResourceFileEntry` keeps a map from ordinal to Type instances.
src/jdk.jlink/share/classes/jdk/tools/jlink/resources/jlink.properties line 119:
> 117: warn.prefix=Warning:
> 118:
> 119: err.runtime.link.not.linkable.runtime=The current run-time image does not support run-time linking.
should we use "runtime" instead of "run-time" in the help message and errors/warnings? "runtime" is already used in the messages in this file.
-------------
PR Review: https://git.openjdk.org/jdk/pull/14787#pullrequestreview-2396177744
PR Review Comment: https://git.openjdk.org/jdk/pull/14787#discussion_r1817204284
PR Review Comment: https://git.openjdk.org/jdk/pull/14787#discussion_r1817236999
PR Review Comment: https://git.openjdk.org/jdk/pull/14787#discussion_r1817238678
PR Review Comment: https://git.openjdk.org/jdk/pull/14787#discussion_r1817241483
PR Review Comment: https://git.openjdk.org/jdk/pull/14787#discussion_r1817240162
PR Review Comment: https://git.openjdk.org/jdk/pull/14787#discussion_r1817246303
PR Review Comment: https://git.openjdk.org/jdk/pull/14787#discussion_r1817251038
PR Review Comment: https://git.openjdk.org/jdk/pull/14787#discussion_r1817219627
PR Review Comment: https://git.openjdk.org/jdk/pull/14787#discussion_r1817225268
PR Review Comment: https://git.openjdk.org/jdk/pull/14787#discussion_r1817226390
PR Review Comment: https://git.openjdk.org/jdk/pull/14787#discussion_r1817229066
PR Review Comment: https://git.openjdk.org/jdk/pull/14787#discussion_r1817228237
PR Review Comment: https://git.openjdk.org/jdk/pull/14787#discussion_r1817230995
PR Review Comment: https://git.openjdk.org/jdk/pull/14787#discussion_r1817231628
PR Review Comment: https://git.openjdk.org/jdk/pull/14787#discussion_r1817233166
PR Review Comment: https://git.openjdk.org/jdk/pull/14787#discussion_r1817253259
PR Review Comment: https://git.openjdk.org/jdk/pull/14787#discussion_r1817253657
PR Review Comment: https://git.openjdk.org/jdk/pull/14787#discussion_r1817205476
PR Review Comment: https://git.openjdk.org/jdk/pull/14787#discussion_r1817261753
PR Review Comment: https://git.openjdk.org/jdk/pull/14787#discussion_r1817208860
PR Review Comment: https://git.openjdk.org/jdk/pull/14787#discussion_r1817216753
PR Review Comment: https://git.openjdk.org/jdk/pull/14787#discussion_r1817265479
PR Review Comment: https://git.openjdk.org/jdk/pull/14787#discussion_r1817269156
More information about the build-dev
mailing list