Skip to content

8357249: Compiler task keeps --system files open#2374

Draft
david-beaumont wants to merge 2 commits into
openjdk:lworldfrom
david-beaumont:JDK_8357249
Draft

8357249: Compiler task keeps --system files open#2374
david-beaumont wants to merge 2 commits into
openjdk:lworldfrom
david-beaumont:JDK_8357249

Conversation

@david-beaumont
Copy link
Copy Markdown
Contributor

@david-beaumont david-beaumont commented Apr 28, 2026

Draft request for discussion purposes only...



Progress

  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue
  • Change must be properly reviewed (1 review required, with at least 1 Committer)

Issue

  • JDK-8357249: Compiler task keeps --system files open (Bug - P4)

Reviewing

Using git

Checkout this PR locally:
$ git fetch https://git.openjdk.org/valhalla.git pull/2374/head:pull/2374
$ git checkout pull/2374

Update a local copy of the PR:
$ git checkout pull/2374
$ git pull https://git.openjdk.org/valhalla.git pull/2374/head

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 2374

View PR using the GUI difftool:
$ git pr show -t 2374

Using diff file

Download this PR as a diff file:
https://git.openjdk.org/valhalla/pull/2374.diff

@bridgekeeper
Copy link
Copy Markdown

bridgekeeper Bot commented Apr 28, 2026

👋 Welcome back dbeaumont! A progress list of the required criteria for merging this PR into lworld will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.

@openjdk
Copy link
Copy Markdown

openjdk Bot commented Apr 28, 2026

❗ This change is not yet ready to be integrated.
See the Progress checklist in the description for automated requirements.

@openjdk
Copy link
Copy Markdown

openjdk Bot commented Apr 28, 2026

@david-beaumont this pull request can not be integrated into lworld due to one or more merge conflicts. To resolve these merge conflicts and update this pull request you can run the following commands in the local repository for your personal fork:

git checkout JDK_8357249
git fetch https://git.openjdk.org/valhalla.git lworld
git merge FETCH_HEAD
# resolve conflicts and follow the instructions given by git merge
git commit -m "Merge lworld"
git push

@openjdk openjdk Bot added the merge-conflict Pull request has merge conflict with target branch label Apr 28, 2026
Copy link
Copy Markdown
Contributor Author

@david-beaumont david-beaumont left a comment

Choose a reason for hiding this comment

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

Discussion points and notes added.

isSystemProperty("sun.arch.data.model", "64", "32");
private static final boolean USE_JVM_MAP =
isSystemProperty("jdk.image.use.jvm.map", "true", "true");
// Whether to map the entire file contents.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It took me ages to realise this isn't "map all image files that are opened", but rather "map an entire image file if it is being mapped at all"

private final FileChannel channel;
private final ImageHeader header;
private final long indexSize;
private final int indexSize;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Just in passing since it's not a long from the getter.

this.name = this.imagePath.toString();

// Only the runtime image is loaded with the root class-loader.
final boolean isRuntimeImage = BasicImageReader.class.getClassLoader() == null;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Semantic readability/intent.

if (isRuntimeImage) {
map = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());
} else {
// Non-runtime images are used for compiler tasks, and we avoid
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is what fixes the non-runtime jimage closing issue.
It's nasty because it goes from memory mapped file to 100M of copied data.

An alternative (with performance cost) would be to do only the "partial" loading of the buffer, say up to the index, and then fetch file content with normal file reads. But this is all open to much discussion.

Copy link
Copy Markdown
Contributor

@AlanBateman AlanBateman Apr 29, 2026

Choose a reason for hiding this comment

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

The changes look right but "Non-runtime images" is a not great phase. You could speak of the current run-time image vs. a target run-time image. That way the comments are clear - use memory-mapping for the current run-time image, regular file I/O for other run-time images.

if ((long) imageFileSize != channel.size()) {
throw new IOException("\"" + name + "\" too large");
}
map = readDirectBuffer(channel, imageFileSize, name);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Pulled out a little helper since it's used in 3 places.


private volatile boolean closed;
// Set to null when closed to allow GC of underlying buffers to unmap files.
private final AtomicReference<SharedImageReader> reader;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This class is the place where we've already got lifetime semantics.
By letting the reader be cleared, we can replace the closed flag and retain non-locking patterns of use.
The benefit is that now the underlying reader becomes GC-able when the ImageReader is closed.

While this may not be of benefit when using direct buffers, it does let any mapped buffers get GC-ed sooner, which is required for closing the underlying memory mapped file.

I think this change is reasonable in general and just improves the semantics of the way this class's lifetime is managed wrt GC.

isOpen = false;
image.close();
image = null;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is the change that lets the JAR file be closed. I hate it!
However it shows that there's potential for passing a "closer" or some kind into the JrtFileSystem from the provider to close the underling JarLoader (which is the actual thing that needs closing).

More to look at here, but this is proof of concept at least.

useCtProps = false;
}
if (useCtProps && JRTIndex.isAvailable()) {
JRTIndex index = null;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removes need for JRTIndex.isAvailable() which opened the uncloseable image in order to do its test.
We can just try and open the thing we want to open instead.
This should be identical in behaviour to what's there now and is necessary for preventing the non-runtime image being held open forever.

}
}

/** {@return whether the JRT file-system is available to create an index} */
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed just to prove it's not needed anywhere else.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm pretty sure this dates back to before the jrt file system worked with exploded builds.

}
}

/** {@return whether the JRT file-system is available to create an index} */
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm pretty sure this dates back to before the jrt file system worked with exploded builds.

ClassLoader cl = provider.getClass().getClassLoader();
if (cl instanceof URLClassLoader) {
((URLClassLoader) cl).close();
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

JrtFileSystemProvider creates the JrtFsLoader/URLClassLoader and that might be a better place to close it. A postClose or some other callback to JrtFileSystemProvider could do this. It's just a suggestion to encapsulate everything about the custom and closeable class loader in one place.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I tried looking at this, but there's no nice way to pass the classloader, or some delegate callback across the boundary (since you can't easily add a new API because you might be talking to old versions of the code).
Unless you're will to pass the class-loader in the env map, but that's pretty nasty.
I do really want to neaten this up, but might not get time.

Copy link
Copy Markdown
Contributor

@AlanBateman AlanBateman Apr 29, 2026

Choose a reason for hiding this comment

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

Okay, I was thinking something simple like provider.afterClose(this) so it's local to the jrtfs implementation (so version mismatch issues). We can look at it another time.

if (isRuntimeImage) {
map = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());
} else {
// Non-runtime images are used for compiler tasks, and we avoid
Copy link
Copy Markdown
Contributor

@AlanBateman AlanBateman Apr 29, 2026

Choose a reason for hiding this comment

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

The changes look right but "Non-runtime images" is a not great phase. You could speak of the current run-time image vs. a target run-time image. That way the comments are clear - use memory-mapping for the current run-time image, regular file I/O for other run-time images.

}
map.rewind();
return map;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I assume this doesn't really need to be a direct buffer, it will work equally well with a heap buffer.

In terms of naming, "readDirectBuffer" doesn't "read a direct buffer". It can be just "read". Same thing for the pre-existing "readBuffer".

As regards the != size check. A short read is possible, meaning it would be valid for read to return a value < size.

Copy link
Copy Markdown
Contributor

@AlanBateman AlanBateman left a comment

Choose a reason for hiding this comment

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

Are you going to resolve the merge conflict and make the PR ready for review?

/**
* Reads the image file to return a newly allocated buffer.
*/
private ByteBuffer copyBuffer(int size)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This reads "size" bytes from the start of the file into a buffer, and returns it. So something like readNBytes is a more appropriate name.

@david-beaumont
Copy link
Copy Markdown
Contributor Author

I'm not necessarily happy with this as it stands. It's more proof of concept.
I also don't like it being on lworld.
Once my preview mode changes are in, some/all of these changes can/should go directly into mainline.

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

Labels

merge-conflict Pull request has merge conflict with target branch

Development

Successfully merging this pull request may close these issues.

2 participants