Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 97 additions & 86 deletions src/java.base/share/classes/jdk/internal/jimage/BasicImageReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<Void>() {
@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<Void>() {
@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() {
Expand Down Expand Up @@ -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);
Expand All @@ -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");
}
Expand Down
52 changes: 26 additions & 26 deletions src/java.base/share/classes/jdk/internal/jimage/ImageReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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<SharedImageReader> reader;

private ImageReader(SharedImageReader reader) {
this.reader = reader;
this.reader = new AtomicReference<>(reader);
}

/**
Expand Down Expand Up @@ -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;
}

/**
Expand All @@ -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);
}

/**
Expand All @@ -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);
}

/**
Expand All @@ -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);
}

/**
Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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();
}
}
}

Expand Down
Loading
Loading