8260337: Optimize ImageReader lookup, used by Class.getResource

Reviewed-by: jlaskey, sundar
This commit is contained in:
Claes Redestad 2021-02-09 15:32:36 +00:00
parent f0bd9db5c7
commit 2f893c2b83
8 changed files with 333 additions and 133 deletions

View file

@ -3048,15 +3048,19 @@ public final class Class<T> implements java.io.Serializable,
}
/**
* Add a package name prefix if the name is not absolute Remove leading "/"
* Add a package name prefix if the name is not absolute. Remove leading "/"
* if name is absolute
*/
private String resolveName(String name) {
if (!name.startsWith("/")) {
Class<?> c = isArray() ? elementType() : this;
String baseName = c.getPackageName();
String baseName = getPackageName();
if (baseName != null && !baseName.isEmpty()) {
name = baseName.replace('.', '/') + "/" + name;
int len = baseName.length() + 1 + name.length();
StringBuilder sb = new StringBuilder(len);
name = sb.append(baseName.replace('.', '/'))
.append('/')
.append(name)
.toString();
}
} else {
name = name.substring(1);

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2014, 2021, 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
@ -249,59 +249,71 @@ public class BasicImageReader implements AutoCloseable {
return stringsReader;
}
public synchronized ImageLocation findLocation(String module, String name) {
Objects.requireNonNull(module);
Objects.requireNonNull(name);
// Details of the algorithm used here can be found in
// jdk.tools.jlink.internal.PerfectHashBuilder.
int count = header.getTableLength();
int index = redirect.get(ImageStringsReader.hashCode(module, name) % count);
public ImageLocation findLocation(String module, String name) {
int index = getLocationIndex(module, name);
if (index < 0) {
// index is twos complement of location attributes index.
index = -index - 1;
} else if (index > 0) {
// index is hash seed needed to compute location attributes index.
index = ImageStringsReader.hashCode(module, name, index) % count;
} else {
// No entry.
return null;
}
long[] attributes = getAttributes(offsets.get(index));
if (!ImageLocation.verify(module, name, attributes, stringsReader)) {
return null;
}
return new ImageLocation(attributes, stringsReader);
}
public synchronized ImageLocation findLocation(String name) {
Objects.requireNonNull(name);
// Details of the algorithm used here can be found in
// jdk.tools.jlink.internal.PerfectHashBuilder.
int count = header.getTableLength();
int index = redirect.get(ImageStringsReader.hashCode(name) % count);
public ImageLocation findLocation(String name) {
int index = getLocationIndex(name);
if (index < 0) {
// index is twos complement of location attributes index.
index = -index - 1;
} else if (index > 0) {
// index is hash seed needed to compute location attributes index.
index = ImageStringsReader.hashCode(name, index) % count;
} else {
// No entry.
return null;
}
long[] attributes = getAttributes(offsets.get(index));
if (!ImageLocation.verify(name, attributes, stringsReader)) {
return null;
}
return new ImageLocation(attributes, stringsReader);
}
public boolean verifyLocation(String module, String name) {
int index = getLocationIndex(module, name);
if (index < 0) {
return false;
}
int locationOffset = offsets.get(index);
return ImageLocation.verify(module, name, locations, locationOffset, stringsReader);
}
// Details of the algorithm used here can be found in
// jdk.tools.jlink.internal.PerfectHashBuilder.
public int getLocationIndex(String name) {
int count = header.getTableLength();
int index = redirect.get(ImageStringsReader.hashCode(name) % count);
if (index < 0) {
// index is twos complement of location attributes index.
return -index - 1;
} else if (index > 0) {
// index is hash seed needed to compute location attributes index.
return ImageStringsReader.hashCode(name, index) % count;
} else {
// No entry.
return -1;
}
}
private int getLocationIndex(String module, String name) {
int count = header.getTableLength();
int index = redirect.get(ImageStringsReader.hashCode(module, name) % count);
if (index < 0) {
// index is twos complement of location attributes index.
return -index - 1;
} else if (index > 0) {
// index is hash seed needed to compute location attributes index.
return ImageStringsReader.hashCode(module, name, index) % count;
} else {
// No entry.
return -1;
}
}
public String[] getEntryNames() {
int[] attributeOffsets = new int[offsets.capacity()];
offsets.get(attributeOffsets);
@ -320,18 +332,21 @@ public class BasicImageReader implements AutoCloseable {
if (offset < 0 || offset >= locations.limit()) {
throw new IndexOutOfBoundsException("offset");
}
ByteBuffer buffer = slice(locations, offset, locations.limit() - offset);
return ImageLocation.decompress(buffer);
return ImageLocation.decompress(locations, offset);
}
public String getString(int offset) {
if (offset < 0 || offset >= strings.limit()) {
throw new IndexOutOfBoundsException("offset");
}
return ImageStringsReader.stringFromByteBuffer(strings, offset);
}
ByteBuffer buffer = slice(strings, offset, strings.limit() - offset);
return ImageStringsReader.stringFromByteBuffer(buffer);
public int match(int offset, String string, int stringOffset) {
if (offset < 0 || offset >= strings.limit()) {
throw new IndexOutOfBoundsException("offset");
}
return ImageStringsReader.stringFromByteBufferMatches(strings, offset, string, stringOffset);
}
private byte[] getBufferBytes(ByteBuffer buffer) {

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2014, 2021, 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
@ -59,41 +59,26 @@ public class ImageLocation {
return strings;
}
static long[] decompress(ByteBuffer bytes) {
static long[] decompress(ByteBuffer bytes, int offset) {
Objects.requireNonNull(bytes);
long[] attributes = new long[ATTRIBUTE_COUNT];
if (bytes != null) {
while (bytes.hasRemaining()) {
int data = bytes.get() & 0xFF;
int kind = data >>> 3;
if (kind == ATTRIBUTE_END) {
break;
}
if (kind < ATTRIBUTE_END || ATTRIBUTE_COUNT <= kind) {
throw new InternalError(
"Invalid jimage attribute kind: " + kind);
}
int length = (data & 0x7) + 1;
long value = 0;
for (int j = 0; j < length; j++) {
value <<= 8;
if (!bytes.hasRemaining()) {
throw new InternalError("Missing jimage attribute data");
}
value |= bytes.get() & 0xFF;
}
attributes[kind] = value;
int limit = bytes.limit();
while (offset < limit) {
int data = bytes.get(offset++) & 0xFF;
if (data <= 0x7) { // ATTRIBUTE_END
break;
}
int kind = data >>> 3;
if (ATTRIBUTE_COUNT <= kind) {
throw new InternalError(
"Invalid jimage attribute kind: " + kind);
}
}
int length = (data & 0x7) + 1;
attributes[kind] = readValue(length, bytes, offset, limit);
offset += length;
}
return attributes;
}
@ -126,51 +111,105 @@ public class ImageLocation {
/**
* A simpler verification would be {@code name.equals(getFullName())}, but
* by not creating the full name and enabling early returns we allocate
* fewer objects. Could possibly be made allocation free by extending
* ImageStrings to test if strings at an offset match the name region.
* fewer objects.
*/
static boolean verify(String name, long[] attributes, ImageStrings strings) {
Objects.requireNonNull(name);
final int length = name.length();
int index = 0;
int moduleOffset = (int)attributes[ATTRIBUTE_MODULE];
if (moduleOffset != 0) {
String module = strings.get(moduleOffset);
final int moduleLen = module.length();
if (moduleOffset != 0 && length >= 1) {
int moduleLen = strings.match(moduleOffset, name, 1);
index = moduleLen + 1;
if (length <= index
if (moduleLen < 0
|| length <= index
|| name.charAt(0) != '/'
|| !name.regionMatches(1, module, 0, moduleLen)
|| name.charAt(index++) != '/') {
return false;
}
}
return verifyName(null, name, index, length, 0,
(int) attributes[ATTRIBUTE_PARENT],
(int) attributes[ATTRIBUTE_BASE],
(int) attributes[ATTRIBUTE_EXTENSION],
strings);
}
return verifyName(name, index, length, attributes, strings);
static boolean verify(String module, String name, ByteBuffer locations,
int locationOffset, ImageStrings strings) {
int moduleOffset = 0;
int parentOffset = 0;
int baseOffset = 0;
int extOffset = 0;
int limit = locations.limit();
while (locationOffset < limit) {
int data = locations.get(locationOffset++) & 0xFF;
if (data <= 0x7) { // ATTRIBUTE_END
break;
}
int kind = data >>> 3;
if (ATTRIBUTE_COUNT <= kind) {
throw new InternalError(
"Invalid jimage attribute kind: " + kind);
}
int length = (data & 0x7) + 1;
switch (kind) {
case ATTRIBUTE_MODULE:
moduleOffset = (int) readValue(length, locations, locationOffset, limit);
break;
case ATTRIBUTE_BASE:
baseOffset = (int) readValue(length, locations, locationOffset, limit);
break;
case ATTRIBUTE_PARENT:
parentOffset = (int) readValue(length, locations, locationOffset, limit);
break;
case ATTRIBUTE_EXTENSION:
extOffset = (int) readValue(length, locations, locationOffset, limit);
break;
}
locationOffset += length;
}
return verifyName(module, name, 0, name.length(),
moduleOffset, parentOffset, baseOffset, extOffset, strings);
}
private static long readValue(int length, ByteBuffer buffer, int offset, int limit) {
long value = 0;
for (int j = 0; j < length; j++) {
value <<= 8;
if (offset >= limit) {
throw new InternalError("Missing jimage attribute data");
}
value |= buffer.get(offset++) & 0xFF;
}
return value;
}
static boolean verify(String module, String name, long[] attributes,
ImageStrings strings) {
Objects.requireNonNull(module);
Objects.requireNonNull(name);
int moduleOffset = (int)attributes[ATTRIBUTE_MODULE];
return verifyName(module, name, 0, name.length(),
(int) attributes[ATTRIBUTE_MODULE],
(int) attributes[ATTRIBUTE_PARENT],
(int) attributes[ATTRIBUTE_BASE],
(int) attributes[ATTRIBUTE_EXTENSION],
strings);
}
private static boolean verifyName(String module, String name, int index, int length,
int moduleOffset, int parentOffset, int baseOffset, int extOffset, ImageStrings strings) {
if (moduleOffset != 0) {
if (!module.equals(strings.get(moduleOffset))) {
if (strings.match(moduleOffset, module, 0) != module.length()) {
return false;
}
}
return verifyName(name, 0, name.length(), attributes, strings);
}
private static boolean verifyName(String name, int index, int length,
long[] attributes, ImageStrings strings) {
int parentOffset = (int) attributes[ATTRIBUTE_PARENT];
if (parentOffset != 0) {
String parent = strings.get(parentOffset);
final int parentLen = parent.length();
if (!name.regionMatches(index, parent, 0, parentLen)) {
int parentLen = strings.match(parentOffset, name, index);
if (parentLen < 0) {
return false;
}
index += parentLen;
@ -178,19 +217,19 @@ public class ImageLocation {
return false;
}
}
String base = strings.get((int) attributes[ATTRIBUTE_BASE]);
final int baseLen = base.length();
if (!name.regionMatches(index, base, 0, baseLen)) {
int baseLen = strings.match(baseOffset, name, index);
if (baseLen < 0) {
return false;
}
index += baseLen;
int extOffset = (int) attributes[ATTRIBUTE_EXTENSION];
if (extOffset != 0) {
String extension = strings.get(extOffset);
int extLen = extension.length();
if (length <= index
|| name.charAt(index++) != '.'
|| !name.regionMatches(index, extension, 0, extLen)) {
|| name.charAt(index++) != '.') {
return false;
}
int extLen = strings.match(extOffset, name, index);
if (extLen < 0) {
return false;
}
index += extLen;
@ -203,7 +242,6 @@ public class ImageLocation {
throw new InternalError(
"Invalid jimage attribute kind: " + kind);
}
return attributes[kind];
}
@ -212,7 +250,6 @@ public class ImageLocation {
throw new InternalError(
"Invalid jimage attribute kind: " + kind);
}
return getStrings().get((int)attributes[kind]);
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014, 2020, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2014, 2021, 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
@ -148,6 +148,11 @@ public final class ImageReader implements AutoCloseable {
return reader.findLocation(mn, rn);
}
public boolean verifyLocation(String mn, String rn) {
requireOpen();
return reader.verifyLocation(mn, rn);
}
public ImageLocation findLocation(String name) {
requireOpen();
return reader.findLocation(name);
@ -740,7 +745,7 @@ public final class ImageReader implements AutoCloseable {
public void walk(Consumer<? super Node> consumer) {
consumer.accept(this);
for ( Node child : children ) {
for (Node child : children) {
if (child.isDirectory()) {
((Directory)child).walk(consumer);
} else {

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2014, 2021, 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
@ -33,7 +33,17 @@ package jdk.internal.jimage;
* to the jimage file provided by the shipped JDK by tools running on JDK 8.
*/
public interface ImageStrings {
public String get(int offset);
String get(int offset);
int add(final String string);
/**
* If there's a string at {@code offset} matching in full a substring of
* {@code string} starting at {@code stringOffset}, return the length
* of that string. Otherwise returns -1. Optional operation.
*/
default int match(int offset, String string, int stringOffset) {
throw new UnsupportedOperationException();
}
public int add(final String string);
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2014, 2021, 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
@ -27,6 +27,7 @@ package jdk.internal.jimage;
import java.io.UTFDataFormatException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
/**
@ -51,6 +52,11 @@ public class ImageStringsReader implements ImageStrings {
return reader.getString(offset);
}
@Override
public int match(int offset, String string, int stringOffset) {
return reader.match(offset, string, stringOffset);
}
@Override
public int add(final String string) {
throw new InternalError("Can not add strings at runtime");
@ -69,9 +75,9 @@ public class ImageStringsReader implements ImageStrings {
}
public static int hashCode(String module, String name, int seed) {
seed = unmaskedHashCode("/", seed);
seed = (seed * HASH_MULTIPLIER) ^ ('/');
seed = unmaskedHashCode(module, seed);
seed = unmaskedHashCode("/", seed);
seed = (seed * HASH_MULTIPLIER) ^ ('/');
seed = unmaskedHashCode(name, seed);
return seed & POSITIVE_MASK;
}
@ -81,8 +87,7 @@ public class ImageStringsReader implements ImageStrings {
byte[] buffer = null;
for (int i = 0; i < slen; i++) {
char ch = s.charAt(i);
int uch = ch & 0xFFFF;
int uch = s.charAt(i);
if ((uch & ~0x7F) != 0) {
if (buffer == null) {
@ -183,29 +188,38 @@ public class ImageStringsReader implements ImageStrings {
return stringFromMUTF8(bytes, 0, bytes.length);
}
static int charsFromByteBufferLength(ByteBuffer buffer) {
/**
* Calculates the number of characters in the String present at the
* specified offset. As an optimization, the length returned will
* be positive if the characters are all ASCII, and negative otherwise.
*/
private static int charsFromByteBufferLength(ByteBuffer buffer, int offset) {
int length = 0;
while(buffer.hasRemaining()) {
byte ch = buffer.get();
int limit = buffer.limit();
boolean asciiOnly = true;
while (offset < limit) {
byte ch = buffer.get(offset++);
if (ch == 0) {
return length;
if (ch < 0) {
asciiOnly = false;
} else if (ch == 0) {
return asciiOnly ? length : -length;
}
if ((ch & 0xC0) != 0x80) {
length++;
}
}
throw new InternalError("No terminating zero byte for modified UTF-8 byte sequence");
}
static void charsFromByteBuffer(char[] chars, ByteBuffer buffer) {
private static void charsFromByteBuffer(char[] chars, ByteBuffer buffer, int offset) {
int j = 0;
while(buffer.hasRemaining()) {
byte ch = buffer.get();
int limit = buffer.limit();
while (offset < limit) {
byte ch = buffer.get(offset++);
if (ch == 0) {
return;
@ -218,7 +232,7 @@ public class ImageStringsReader implements ImageStrings {
int mask = 0x40;
while ((uch & mask) != 0) {
ch = buffer.get();
ch = buffer.get(offset++);
if ((ch & 0xC0) != 0x80) {
throw new InternalError("Bad continuation in " +
@ -242,14 +256,62 @@ public class ImageStringsReader implements ImageStrings {
}
public static String stringFromByteBuffer(ByteBuffer buffer) {
int length = charsFromByteBufferLength(buffer);
buffer.rewind();
char[] chars = new char[length];
charsFromByteBuffer(chars, buffer);
return stringFromByteBuffer(buffer, 0);
}
/* package-private */
static String stringFromByteBuffer(ByteBuffer buffer, int offset) {
int length = charsFromByteBufferLength(buffer, offset);
if (length > 0) {
byte[] asciiBytes = new byte[length];
// Ideally we could use buffer.get(offset, asciiBytes, 0, length)
// here, but that was introduced in JDK 13
for (int i = 0; i < length; i++) {
asciiBytes[i] = buffer.get(offset++);
}
return new String(asciiBytes, StandardCharsets.US_ASCII);
}
char[] chars = new char[-length];
charsFromByteBuffer(chars, buffer, offset);
return new String(chars);
}
/* package-private */
static int stringFromByteBufferMatches(ByteBuffer buffer, int offset, String string, int stringOffset) {
// ASCII fast-path
int limit = buffer.limit();
int current = offset;
int slen = string.length();
while (current < limit) {
byte ch = buffer.get(current);
if (ch <= 0) {
if (ch == 0) {
// Match
return current - offset;
}
// non-ASCII byte, run slow-path from current offset
break;
}
if (slen <= stringOffset || string.charAt(stringOffset) != (char)ch) {
// No match
return -1;
}
stringOffset++;
current++;
}
// invariant: remainder of the string starting at current is non-ASCII,
// so return value from charsFromByteBufferLength will be negative
int length = -charsFromByteBufferLength(buffer, current);
char[] chars = new char[length];
charsFromByteBuffer(chars, buffer, current);
for (int i = 0; i < length; i++) {
if (string.charAt(stringOffset++) != chars[i]) {
return -1;
}
}
return length;
}
static int mutf8FromStringLength(String s) {
int length = 0;
int slen = s.length();

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2015, 2021, 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
@ -432,11 +432,27 @@ public final class SystemModuleFinders {
}
}
/**
* Returns {@code true} if the given resource exists, {@code false}
* if not found.
*/
private boolean containsImageLocation(String name) throws IOException {
Objects.requireNonNull(name);
if (closed)
throw new IOException("ModuleReader is closed");
ImageReader imageReader = SystemImage.reader();
if (imageReader != null) {
return imageReader.verifyLocation(module, name);
} else {
// not an images build
return false;
}
}
@Override
public Optional<URI> find(String name) throws IOException {
ImageLocation location = findImageLocation(name);
if (location != null) {
URI u = URI.create("jrt:/" + module + "/" + name);
if (containsImageLocation(name)) {
URI u = JNUA.create("jrt", "/" + module + "/" + name);
return Optional.of(u);
} else {
return Optional.empty();