Skip to content

Commit d49f210

Browse files
Eirik Bjørsnøscl4es
Eirik Bjørsnøs
andcommittedOct 28, 2024
8342040: Further improve entry lookup performance for multi-release JARs
Co-authored-by: Claes Redestad <redestad@openjdk.org> Reviewed-by: redestad
1 parent d2e716e commit d49f210

File tree

4 files changed

+64
-63
lines changed

4 files changed

+64
-63
lines changed
 

‎src/java.base/share/classes/java/util/jar/JarFile.java

+10-19
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import java.lang.ref.SoftReference;
4141
import java.security.CodeSigner;
4242
import java.security.cert.Certificate;
43+
import java.util.BitSet;
4344
import java.util.Enumeration;
4445
import java.util.List;
4546
import java.util.Objects;
@@ -597,26 +598,16 @@ private String getBasename(String name) {
597598
}
598599

599600
private JarEntry getVersionedEntry(String name, JarEntry defaultEntry) {
600-
if (!name.startsWith(META_INF)) {
601-
int[] versions = JUZFA.getMetaInfVersions(this);
602-
if (BASE_VERSION_FEATURE < versionFeature && versions.length > 0) {
603-
// search for versioned entry
604-
for (int i = versions.length - 1; i >= 0; i--) {
605-
int version = versions[i];
606-
// skip versions above versionFeature
607-
if (version > versionFeature) {
608-
continue;
609-
}
610-
// skip versions below base version
611-
if (version < BASE_VERSION_FEATURE) {
612-
break;
613-
}
614-
JarFileEntry vje = (JarFileEntry)super.getEntry(
615-
META_INF_VERSIONS + version + "/" + name);
616-
if (vje != null) {
617-
return vje.withBasename(name);
618-
}
601+
if (BASE_VERSION_FEATURE < versionFeature && !name.startsWith(META_INF)) {
602+
BitSet versions = JUZFA.getMetaInfVersions(this, name);
603+
int version = versions.previousSetBit(versionFeature);
604+
while (version >= BASE_VERSION_FEATURE) {
605+
JarFileEntry vje = (JarFileEntry)super.getEntry(
606+
META_INF_VERSIONS + version + "/" + name);
607+
if (vje != null) {
608+
return vje.withBasename(name);
619609
}
610+
version = versions.previousSetBit(version - 1);
620611
}
621612
}
622613
return defaultEntry;

‎src/java.base/share/classes/java/util/zip/ZipFile.java

+32-40
Original file line numberDiff line numberDiff line change
@@ -37,23 +37,7 @@
3737
import java.nio.file.InvalidPathException;
3838
import java.nio.file.attribute.BasicFileAttributes;
3939
import java.nio.file.Files;
40-
import java.util.ArrayDeque;
41-
import java.util.ArrayList;
42-
import java.util.Arrays;
43-
import java.util.Collections;
44-
import java.util.Deque;
45-
import java.util.Enumeration;
46-
import java.util.HashMap;
47-
import java.util.Iterator;
48-
import java.util.List;
49-
import java.util.Locale;
50-
import java.util.Objects;
51-
import java.util.NoSuchElementException;
52-
import java.util.Set;
53-
import java.util.Spliterator;
54-
import java.util.Spliterators;
55-
import java.util.TreeSet;
56-
import java.util.WeakHashMap;
40+
import java.util.*;
5741
import java.util.function.Consumer;
5842
import java.util.function.IntFunction;
5943
import java.util.jar.JarEntry;
@@ -64,6 +48,7 @@
6448
import jdk.internal.access.JavaUtilJarAccess;
6549
import jdk.internal.access.SharedSecrets;
6650
import jdk.internal.util.ArraysSupport;
51+
import jdk.internal.util.DecimalDigits;
6752
import jdk.internal.util.OperatingSystem;
6853
import jdk.internal.perf.PerfCounter;
6954
import jdk.internal.ref.CleanerFactory;
@@ -1090,19 +1075,23 @@ private String getManifestName(boolean onlyIfSignatureRelatedFiles) {
10901075
}
10911076

10921077
/**
1093-
* Returns the versions for which there exists a non-directory
1094-
* entry that begin with "META-INF/versions/" (case ignored).
1078+
* Returns a BitSet where the set bits represents versions found for
1079+
* the given entry name. For performance reasons, the name is looked
1080+
* up only by hashcode, meaning the result is an over-approximation.
10951081
* This method is used in JarFile, via SharedSecrets, as an
10961082
* optimization when looking up potentially versioned entries.
1097-
* Returns an empty array if no versioned entries exist.
1083+
* Returns an empty BitSet if no versioned entries exist for this
1084+
* name.
10981085
*/
1099-
private int[] getMetaInfVersions() {
1086+
private BitSet getMetaInfVersions(String name) {
11001087
synchronized (this) {
11011088
ensureOpen();
1102-
return res.zsrc.metaVersions;
1089+
return res.zsrc.metaVersions.getOrDefault(ZipCoder.hash(name), EMPTY_VERSIONS);
11031090
}
11041091
}
11051092

1093+
private static final BitSet EMPTY_VERSIONS = new BitSet();
1094+
11061095
/**
11071096
* Returns the value of the System property which indicates whether the
11081097
* Extra ZIP64 validation should be disabled.
@@ -1139,8 +1128,8 @@ public String getManifestName(JarFile jar, boolean onlyIfHasSignatureRelatedFile
11391128
return ((ZipFile)jar).getManifestName(onlyIfHasSignatureRelatedFiles);
11401129
}
11411130
@Override
1142-
public int[] getMetaInfVersions(JarFile jar) {
1143-
return ((ZipFile)jar).getMetaInfVersions();
1131+
public BitSet getMetaInfVersions(JarFile jar, String name) {
1132+
return ((ZipFile)jar).getMetaInfVersions(name);
11441133
}
11451134
@Override
11461135
public Enumeration<JarEntry> entries(ZipFile zip) {
@@ -1175,7 +1164,8 @@ private static class Source {
11751164
private static final JavaUtilJarAccess JUJA = SharedSecrets.javaUtilJarAccess();
11761165
// "META-INF/".length()
11771166
private static final int META_INF_LEN = 9;
1178-
private static final int[] EMPTY_META_VERSIONS = new int[0];
1167+
// "META-INF/versions//".length()
1168+
private static final int META_INF_VERSIONS_LEN = 19;
11791169
// CEN size is limited to the maximum array size in the JDK
11801170
private static final int MAX_CEN_SIZE = ArraysSupport.SOFT_MAX_ARRAY_LENGTH;
11811171

@@ -1192,7 +1182,7 @@ private static class Source {
11921182
private int manifestPos = -1; // position of the META-INF/MANIFEST.MF, if exists
11931183
private int manifestNum = 0; // number of META-INF/MANIFEST.MF, case insensitive
11941184
private int[] signatureMetaNames; // positions of signature related entries, if such exist
1195-
private int[] metaVersions; // list of unique versions found in META-INF/versions/
1185+
private Map<Integer, BitSet> metaVersions; // Versions found in META-INF/versions/, by entry name hash
11961186
private final boolean startsWithLoc; // true, if ZIP file starts with LOCSIG (usually true)
11971187

11981188
// A Hashmap for all entries.
@@ -1574,7 +1564,7 @@ private void close() throws IOException {
15741564
manifestPos = -1;
15751565
manifestNum = 0;
15761566
signatureMetaNames = null;
1577-
metaVersions = EMPTY_META_VERSIONS;
1567+
metaVersions = null;
15781568
}
15791569

15801570
private static final int BUF_SIZE = 8192;
@@ -1759,8 +1749,6 @@ private void initCEN(int knownTotal) throws IOException {
17591749

17601750
// list for all meta entries
17611751
ArrayList<Integer> signatureNames = null;
1762-
// Set of all version numbers seen in META-INF/versions/
1763-
Set<Integer> metaVersionsSet = null;
17641752

17651753
// Iterate through the entries in the central directory
17661754
int idx = 0; // Index into the entries array
@@ -1799,9 +1787,19 @@ private void initCEN(int knownTotal) throws IOException {
17991787
// performance in multi-release jar files
18001788
int version = getMetaVersion(entryPos + META_INF_LEN, nlen - META_INF_LEN);
18011789
if (version > 0) {
1802-
if (metaVersionsSet == null)
1803-
metaVersionsSet = new TreeSet<>();
1804-
metaVersionsSet.add(version);
1790+
try {
1791+
// Compute hash code of name from "META-INF/versions/{version)/{name}
1792+
int prefixLen = META_INF_VERSIONS_LEN + DecimalDigits.stringSize(version);
1793+
int hashCode = zipCoderForPos(pos).checkedHash(cen,
1794+
entryPos + prefixLen,
1795+
nlen - prefixLen);
1796+
// Register version for this hash code
1797+
if (metaVersions == null)
1798+
metaVersions = new HashMap<>();
1799+
metaVersions.computeIfAbsent(hashCode, _ -> new BitSet()).set(version);
1800+
} catch (Exception e) {
1801+
throw new IllegalArgumentException(e);
1802+
}
18051803
}
18061804
}
18071805
}
@@ -1819,14 +1817,8 @@ private void initCEN(int knownTotal) throws IOException {
18191817
signatureMetaNames[j] = signatureNames.get(j);
18201818
}
18211819
}
1822-
if (metaVersionsSet != null) {
1823-
metaVersions = new int[metaVersionsSet.size()];
1824-
int c = 0;
1825-
for (Integer version : metaVersionsSet) {
1826-
metaVersions[c++] = version;
1827-
}
1828-
} else {
1829-
metaVersions = EMPTY_META_VERSIONS;
1820+
if (metaVersions == null) {
1821+
metaVersions = Map.of();
18301822
}
18311823
if (pos != cen.length) {
18321824
zerror("invalid CEN header (bad header size)");

‎src/java.base/share/classes/jdk/internal/access/JavaUtilZipFileAccess.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
package jdk.internal.access;
2727

28+
import java.util.BitSet;
2829
import java.util.Enumeration;
2930
import java.util.List;
3031
import java.util.jar.JarEntry;
@@ -38,7 +39,7 @@ public interface JavaUtilZipFileAccess {
3839
public List<String> getManifestAndSignatureRelatedFiles(JarFile zip);
3940
public String getManifestName(JarFile zip, boolean onlyIfSignatureRelatedFiles);
4041
public int getManifestNum(JarFile zip);
41-
public int[] getMetaInfVersions(JarFile zip);
42+
public BitSet getMetaInfVersions(JarFile zip, String name);
4243
public Enumeration<JarEntry> entries(ZipFile zip);
4344
public Stream<JarEntry> stream(ZipFile zip);
4445
public Stream<String> entryNameStream(ZipFile zip);

‎test/micro/org/openjdk/bench/java/util/jar/JarFileGetEntry.java

+20-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -31,9 +31,12 @@
3131
import java.nio.file.Files;
3232
import java.util.Random;
3333
import java.util.concurrent.TimeUnit;
34+
import java.util.jar.Attributes;
3435
import java.util.jar.JarFile;
3536
import java.util.jar.JarOutputStream;
37+
import java.util.jar.Manifest;
3638
import java.util.zip.ZipEntry;
39+
import java.util.zip.ZipFile;
3740

3841
/**
3942
* Simple benchmark measuring cost of looking up entries in a jar file.
@@ -71,6 +74,9 @@ public class JarFileGetEntry {
7174
@Param({"512", "1024"})
7275
private int size;
7376

77+
@Param({"false", "true"})
78+
private boolean mr;
79+
7480
public JarFile jarFile;
7581
public String[] entryNames;
7682
public String[] missingEntryNames;
@@ -91,9 +97,20 @@ public void beforeRun() throws IOException {
9197
entryNames = new String[size];
9298
missingEntryNames = new String[size];
9399

100+
Manifest man = new Manifest();
101+
man.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0");
102+
if (mr) {
103+
man.getMainAttributes().put(Attributes.Name.MULTI_RELEASE, "true");
104+
}
94105
try (FileOutputStream fos = new FileOutputStream(tempFile);
95-
JarOutputStream jos = new JarOutputStream(fos)) {
106+
JarOutputStream jos = new JarOutputStream(fos, man)) {
96107

108+
if (mr) {
109+
// Add a few versioned entries
110+
jos.putNextEntry(new ZipEntry("META-INF/versions/9/module-info.class"));
111+
jos.putNextEntry(new ZipEntry("META-INF/versions/17/foo/library/Library.class"));
112+
jos.putNextEntry(new ZipEntry("META-INF/versions/21/foo/library/Library.class"));
113+
}
97114
Random random = new Random(4711);
98115
for (int i = 0; i < size; i++) {
99116
String ename = "entry-" + (random.nextInt(90000) + 10000) + "-" + i;
@@ -107,7 +124,7 @@ public void beforeRun() throws IOException {
107124
}
108125
}
109126

110-
jarFile = new JarFile(tempFile);
127+
jarFile = new JarFile(tempFile, true, ZipFile.OPEN_READ, mr ? JarFile.runtimeVersion() : JarFile.baseVersion());
111128
}
112129

113130
@Benchmark

0 commit comments

Comments
 (0)
Please sign in to comment.