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..fe10ea8c9b5 100644 --- a/src/java.base/share/classes/jdk/internal/jimage/BasicImageReader.java +++ b/src/java.base/share/classes/jdk/internal/jimage/BasicImageReader.java @@ -68,21 +68,32 @@ 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; + // A buffer holding either the entire image file (memory mapped), or just + // the index data (copied). Only the runtime image can be fully mapped, + // but it might not be (see FULLY_MAP_RUNTIME_IMAGE). private final ByteBuffer memoryMap; + // Whether memoryMap contains the complete file content or just the index. + private final boolean isFullyMapped; + // 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" }) @@ -92,18 +103,21 @@ protected BasicImageReader(Path path, ByteOrder byteOrder) this.byteOrder = Objects.requireNonNull(byteOrder); this.name = this.imagePath.toString(); + // Only the runtime image is loaded with the root class-loader. + final boolean isRuntimeImage = BasicImageReader.class.getClassLoader() == null; ByteBuffer map; - if (USE_JVM_MAP && BasicImageReader.class.getClassLoader() == null) { + if (USE_JVM_MAP && isRuntimeImage) { // 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 { + } else { map = null; } // Open the file only if no memory map yet or is 32 bit jvm - if (map != null && MAP_ALL) { + if (map != null && FULLY_MAP_RUNTIME_IMAGE) { + // No channel needed if we have the fully mapped native buffer. channel = null; } else { channel = FileChannel.open(imagePath, StandardOpenOption.READ); @@ -111,7 +125,7 @@ protected BasicImageReader(Path path, ByteOrder byteOrder) AccessController.doPrivileged(new PrivilegedAction() { @Override public Void run() { - if (BasicImageReader.class.getClassLoader() == null) { + if (isRuntimeImage) { try { Class fileChannelImpl = Class.forName("sun.nio.ch.FileChannelImpl"); @@ -132,35 +146,33 @@ public Void run() { }); } - // If no memory map yet and 64 bit jvm then memory map entire file - if (MAP_ALL && map == null) { + // If no memory map yet and 64 bit jvm then memory map the runtime file. + isFullyMapped = isRuntimeImage && FULLY_MAP_RUNTIME_IMAGE; + if (map == null && isFullyMapped) { map = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size()); } - // Assume we have a memory map to read image file header + // headerBuffer is a temporary buffer for reading the heading (which + // may be the full buffer, or a scratch buffer we discard later). 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"); - } + headerBuffer = copyBuffer(headerSize); } else if (headerBuffer.capacity() < headerSize) { + // A fully mapped file cannot be smaller than the header size. throw new IOException("\"" + name + "\" is not an image file"); } - // Interpret the image file header + // Read the header and find the index size, which is the minimum buffer + // size we need to read if we haven't already mapped the entire file. 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); + map = copyBuffer(indexSize); } memoryMap = map.asReadOnlyBuffer(); @@ -402,6 +414,22 @@ private byte[] getBufferBytes(ByteBuffer buffer) { return bytes; } + /** + * Reads the image file to return a newly allocated buffer. + */ + private ByteBuffer copyBuffer(int size) + throws IOException { + ByteBuffer map = ByteBuffer.allocate(size); + if (channel.read(map, 0L) != size) { + throw new IOException("\"" + name + "\" is not an image file"); + } + map.rewind(); + return map; + } + + /** + * Reads entry data either from the mapped buffer, or directly from the channel. + */ private ByteBuffer readBuffer(long offset, long size) { if (offset < 0 || Integer.MAX_VALUE <= offset) { throw new IndexOutOfBoundsException("Bad offset: " + offset); @@ -413,7 +441,7 @@ private ByteBuffer readBuffer(long offset, long size) { } int checkedSize = (int) size; - if (MAP_ALL) { + if (isFullyMapped) { ByteBuffer buffer = slice(memoryMap, checkedOffset, checkedSize); buffer.order(ByteOrder.BIG_ENDIAN); 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. *