diff --git a/src/java.base/share/classes/jdk/internal/jimage/BasicImageReader.java b/src/java.base/share/classes/jdk/internal/jimage/BasicImageReader.java index 446d3576a0d..274b577a4f0 100644 --- a/src/java.base/share/classes/jdk/internal/jimage/BasicImageReader.java +++ b/src/java.base/share/classes/jdk/internal/jimage/BasicImageReader.java @@ -68,122 +68,130 @@ public Boolean run() { isSystemProperty("sun.arch.data.model", "64", "32"); private static final boolean USE_JVM_MAP = isSystemProperty("jdk.image.use.jvm.map", "true", "true"); - private static final boolean MAP_ALL = + // Whether to map the entire file contents for runtime images. + private static final boolean FULLY_MAP_RUNTIME_IMAGE = isSystemProperty("jdk.image.map.all", "true", IS_64_BIT ? "true" : "false"); private final Path imagePath; private final ByteOrder byteOrder; private final String name; + // The memory mapped image file (only used for the runtime image). private final ByteBuffer memoryMap; + // Channel from which file data is loaded (null if using a native buffer). private final FileChannel channel; + // Header information parsed from the start of the image file. private final ImageHeader header; - private final long indexSize; + // Size (bytes) of the index region at the start of the image file. + private final int indexSize; + // Sliced views taken from the index region of memoryMap. private final IntBuffer redirect; private final IntBuffer offsets; private final ByteBuffer locations; private final ByteBuffer strings; private final ImageStringsReader stringsReader; + // Decompressor for resource entries. private final Decompressor decompressor; - @SuppressWarnings({ "removal", "this-escape", "suppression" }) + public static BasicImageReader open(Path imagePath) throws IOException { + return new BasicImageReader(imagePath, ByteOrder.nativeOrder()); + } + + protected BasicImageReader(Path imagePath) throws IOException { + this(imagePath, ByteOrder.nativeOrder()); + } + + @SuppressWarnings({ "this-escape", "suppression" }) protected BasicImageReader(Path path, ByteOrder byteOrder) throws IOException { this.imagePath = Objects.requireNonNull(path); this.byteOrder = Objects.requireNonNull(byteOrder); this.name = this.imagePath.toString(); - ByteBuffer map; - - if (USE_JVM_MAP && BasicImageReader.class.getClassLoader() == null) { - // Check to see if the jvm has opened the file using libjimage - // native entry when loading the image for this runtime - map = NativeImageBuffer.getNativeMap(name); - } else { - map = null; - } + // Only the runtime image is loaded with the root class-loader. + boolean isRuntimeImage = BasicImageReader.class.getClassLoader() == null; - // Open the file only if no memory map yet or is 32 bit jvm - if (map != null && MAP_ALL) { - channel = null; - } else { - channel = FileChannel.open(imagePath, StandardOpenOption.READ); - // No lambdas during bootstrap - AccessController.doPrivileged(new PrivilegedAction() { - @Override - public Void run() { - if (BasicImageReader.class.getClassLoader() == null) { - try { - Class fileChannelImpl = - Class.forName("sun.nio.ch.FileChannelImpl"); - Method setUninterruptible = - fileChannelImpl.getMethod("setUninterruptible"); - setUninterruptible.invoke(channel); - } catch (ClassNotFoundException | - NoSuchMethodException | - IllegalAccessException | - InvocationTargetException ex) { - // fall thru - will only happen on JDK-8 systems where this code - // is only used by tools using jrt-fs (non-critical.) - } - } + // Check to see if the jvm has opened the file using libjimage + // native entry when loading the image for this runtime + ByteBuffer map = + (USE_JVM_MAP && isRuntimeImage) ? NativeImageBuffer.getNativeMap(name) : null; - return null; - } - }); - } + // Open the file channel if we don't have a native memory map. + this.channel = (map == null) ? openFileChannel(imagePath, isRuntimeImage) : null; - // If no memory map yet and 64 bit jvm then memory map entire file - if (MAP_ALL && map == null) { + // Manually map the entire runtime image (if configured to). + if (map == null && isRuntimeImage && FULLY_MAP_RUNTIME_IMAGE) { map = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size()); } - // Assume we have a memory map to read image file header - ByteBuffer headerBuffer = map; - int headerSize = ImageHeader.getHeaderSize(); - - // If no memory map then read header from image file - if (headerBuffer == null) { - headerBuffer = ByteBuffer.allocateDirect(headerSize); - if (channel.read(headerBuffer, 0L) == headerSize) { - headerBuffer.rewind(); - } else { - throw new IOException("\"" + name + "\" is not an image file"); - } - } else if (headerBuffer.capacity() < headerSize) { - throw new IOException("\"" + name + "\" is not an image file"); - } - - // Interpret the image file header - header = readHeader(intBuffer(headerBuffer, 0, headerSize)); - indexSize = header.getIndexSize(); - - // If no memory map yet then must be 32 bit jvm not previously mapped - if (map == null) { - // Just map the image index - map = channel.map(FileChannel.MapMode.READ_ONLY, 0, indexSize); - } - - memoryMap = map.asReadOnlyBuffer(); + // Either we have the entire file mapped and use it for all content, + // or we load the index separately and read entries on demand. + this.memoryMap = map != null ? map.asReadOnlyBuffer() : null; - // Interpret the image index - if (memoryMap.capacity() < indexSize) { - throw new IOException("The image file \"" + name + "\" is corrupted"); - } - redirect = intBuffer(memoryMap, header.getRedirectOffset(), header.getRedirectSize()); - offsets = intBuffer(memoryMap, header.getOffsetsOffset(), header.getOffsetsSize()); - locations = slice(memoryMap, header.getLocationsOffset(), header.getLocationsSize()); - strings = slice(memoryMap, header.getStringsOffset(), header.getStringsSize()); - - stringsReader = new ImageStringsReader(this); - decompressor = new Decompressor(); + // Read the header and find the index size, which is the minimum we need + // to copy if we haven't already mapped the entire file. + int headerSize = ImageHeader.getHeaderSize(); + this.header = readHeader(intBuffer(getOrCopyBuffer(headerSize), 0, headerSize)); + this.indexSize = header.getIndexSize(); + + // Now we have the index size, get the complete index buffer and slice it. + ByteBuffer indexBuffer = getOrCopyBuffer(indexSize); + this.redirect = intBuffer(indexBuffer, header.getRedirectOffset(), header.getRedirectSize()); + this.offsets = intBuffer(indexBuffer, header.getOffsetsOffset(), header.getOffsetsSize()); + this.locations = slice(indexBuffer, header.getLocationsOffset(), header.getLocationsSize()); + this.strings = slice(indexBuffer, header.getStringsOffset(), header.getStringsSize()); + + this.stringsReader = new ImageStringsReader(this); + this.decompressor = new Decompressor(); } - protected BasicImageReader(Path imagePath) throws IOException { - this(imagePath, ByteOrder.nativeOrder()); + @SuppressWarnings({ "removal", "suppression" }) + private static FileChannel openFileChannel(Path imagePath, boolean isRuntimeImage) throws IOException { + final FileChannel channel; + channel = FileChannel.open(imagePath, StandardOpenOption.READ); + // No lambdas during bootstrap + AccessController.doPrivileged(new PrivilegedAction() { + @Override + public Void run() { + if (isRuntimeImage) { + try { + Class fileChannelImpl = + Class.forName("sun.nio.ch.FileChannelImpl"); + Method setUninterruptible = + fileChannelImpl.getMethod("setUninterruptible"); + setUninterruptible.invoke(channel); + } catch (ClassNotFoundException | + NoSuchMethodException | + IllegalAccessException | + InvocationTargetException ex) { + // fall thru - will only happen on JDK-8 systems where this code + // is only used by tools using jrt-fs (non-critical.) + } + } + return null; + } + }); + return channel; } - public static BasicImageReader open(Path imagePath) throws IOException { - return new BasicImageReader(imagePath, ByteOrder.nativeOrder()); + /** + * Gets a buffer from the start of the image file of at least the given size. + * If possible this method just returns the memory mapped file buffer. + */ + private ByteBuffer getOrCopyBuffer(int trustedSize) throws IOException { + if (memoryMap != null) { + if (memoryMap.capacity() < trustedSize) { + throw new IOException("\"" + name + "\" is not an image file"); + } + return memoryMap; + } else { + // Channel must be non-null if there's no memory map. + ByteBuffer buffer = ByteBuffer.allocate(trustedSize); + if (channel.read(buffer, 0L) != trustedSize) { + throw new IOException("\"" + name + "\" is not an image file"); + } + buffer.rewind(); + return buffer; + } } public ImageHeader getHeader() { @@ -402,6 +410,9 @@ private byte[] getBufferBytes(ByteBuffer buffer) { return bytes; } + /** + * Reads entry data either from the mapped buffer, or copied from the channel. + */ private ByteBuffer readBuffer(long offset, long size) { if (offset < 0 || Integer.MAX_VALUE <= offset) { throw new IndexOutOfBoundsException("Bad offset: " + offset); @@ -413,12 +424,12 @@ private ByteBuffer readBuffer(long offset, long size) { } int checkedSize = (int) size; - if (MAP_ALL) { + if (memoryMap != null) { ByteBuffer buffer = slice(memoryMap, checkedOffset, checkedSize); buffer.order(ByteOrder.BIG_ENDIAN); - return buffer; } else { + // If there's no memory map, there must be a file channel. if (channel == null) { throw new InternalError("Image file channel not open"); } diff --git a/src/java.base/share/classes/jdk/internal/jimage/ImageReader.java b/src/java.base/share/classes/jdk/internal/jimage/ImageReader.java index 5cab6055022..980b0648346 100644 --- a/src/java.base/share/classes/jdk/internal/jimage/ImageReader.java +++ b/src/java.base/share/classes/jdk/internal/jimage/ImageReader.java @@ -27,6 +27,7 @@ import jdk.internal.jimage.ImageLocation.LocationType; import java.io.IOException; +import java.io.UncheckedIOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.IntBuffer; @@ -44,6 +45,7 @@ import java.util.Objects; import java.util.Set; import java.util.TreeMap; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Stream; @@ -89,12 +91,11 @@ * to the jimage file provided by the shipped JDK by tools running on JDK 8. */ public final class ImageReader implements AutoCloseable { - private final SharedImageReader reader; - - private volatile boolean closed; + // Set to null when closed to allow GC of underlying buffers to unmap files. + private final AtomicReference reader; private ImageReader(SharedImageReader reader) { - this.reader = reader; + this.reader = new AtomicReference<>(reader); } /** @@ -123,23 +124,19 @@ public static ImageReader open(Path imagePath, ByteOrder byteOrder, PreviewMode @Override public void close() throws IOException { - if (closed) { + SharedImageReader r = reader.getAndSet(null); + if (r == null) { throw new IOException("image file already closed"); } - reader.close(this); - closed = true; + r.close(this); } - private void ensureOpen() throws IOException { - if (closed) { + private SharedImageReader ensureOpen() throws IOException { + SharedImageReader r = reader.get(); + if (r == null) { throw new IOException("image file closed"); } - } - - private void requireOpen() { - if (closed) { - throw new IllegalStateException("image file closed"); - } + return r; } /** @@ -150,8 +147,7 @@ private void requireOpen() { * @return a node representing a resource, directory or symbolic link. */ public Node findNode(String name) throws IOException { - ensureOpen(); - return reader.findNode(name); + return ensureOpen().findNode(name); } /** @@ -170,8 +166,7 @@ public Node findNode(String name) throws IOException { */ public Node findResourceNode(String moduleName, String resourcePath) throws IOException { - ensureOpen(); - return reader.findResourceNode(moduleName, resourcePath); + return ensureOpen().findResourceNode(moduleName, resourcePath); } /** @@ -189,8 +184,7 @@ public Node findResourceNode(String moduleName, String resourcePath) */ public boolean containsResource(String moduleName, String resourcePath) throws IOException { - ensureOpen(); - return reader.containsResource(moduleName, resourcePath); + return ensureOpen().containsResource(moduleName, resourcePath); } /** @@ -202,24 +196,30 @@ public boolean containsResource(String moduleName, String resourcePath) * given node is not a resource node). */ public byte[] getResource(Node node) throws IOException { - ensureOpen(); - return reader.getResource(node); + return ensureOpen().getResource(node); } /** * Returns the content of a resource node in a newly allocated byte buffer. */ public ByteBuffer getResourceBuffer(Node node) { - requireOpen(); if (!node.isResource()) { throw new IllegalArgumentException("Not a resource node: " + node); } - return reader.getResourceBuffer(node.getLocation()); + try { + return ensureOpen().getResourceBuffer(node.getLocation()); + } catch (IOException e) { + throw new UncheckedIOException(e); + } } // Package protected for use only by SystemImageReader. ResourceEntries getResourceEntries() { - return reader.getResourceEntries(); + try { + return ensureOpen().getResourceEntries(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } } private static final class SharedImageReader extends BasicImageReader { diff --git a/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileSystem.java b/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileSystem.java index 4d739d1e200..69d327678b1 100644 --- a/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileSystem.java +++ b/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileSystem.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -28,6 +28,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.net.URLClassLoader; import java.nio.ByteBuffer; import java.nio.channels.Channels; import java.nio.channels.FileChannel; @@ -313,6 +314,12 @@ synchronized void cleanup() throws IOException { isOpen = false; image.close(); image = null; + + // Closes the jrt-fs.jar !!! + ClassLoader cl = provider.getClass().getClassLoader(); + if (cl instanceof URLClassLoader) { + ((URLClassLoader) cl).close(); + } } } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/ClassFinder.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/ClassFinder.java index bfdde1293ee..150ff2c4577 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/ClassFinder.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/ClassFinder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,7 +26,9 @@ package com.sun.tools.javac.code; import java.io.IOException; +import java.nio.file.FileSystemNotFoundException; import java.nio.file.Path; +import java.nio.file.ProviderNotFoundException; import java.util.EnumSet; import java.util.HashMap; import java.util.Iterator; @@ -217,14 +219,20 @@ protected ClassFinder(Context context) { } else { useCtProps = false; } - if (useCtProps && JRTIndex.isAvailable()) { + JRTIndex index = null; + if (useCtProps) { Preview preview = Preview.instance(context); - JavaCompiler comp = JavaCompiler.instance(context); - jrtIndex = JRTIndex.instance(preview.isEnabled()); - comp.closeables = comp.closeables.prepend(jrtIndex); - } else { - jrtIndex = null; + try { + index = JRTIndex.instance(preview.isEnabled()); + } catch (ProviderNotFoundException | FileSystemNotFoundException e) { + // Leave index null. + } + if (index != null) { + JavaCompiler comp = JavaCompiler.instance(context); + comp.closeables = comp.closeables.prepend(index); + } } + jrtIndex = index; profile = Profile.instance(context); cachedCompletionFailure = new CompletionFailure(null, () -> null, dcfh); diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/file/JRTIndex.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/file/JRTIndex.java index 1688a03d19f..0a9625ee47e 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/file/JRTIndex.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/file/JRTIndex.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -98,16 +98,6 @@ public static JRTIndex instance(boolean previewMode) { } } - /** {@return whether the JRT file-system is available to create an index} */ - public static boolean isAvailable() { - try { - FileSystems.getFileSystem(URI.create("jrt:/")); - return true; - } catch (ProviderNotFoundException | FileSystemNotFoundException e) { - return false; - } - } - /** * Underlying file system resources potentially shared between many indexes. *