changeset 58877:3d69461c344e

8242596: Improve JarFile.getEntry performance for multi-release jar files Reviewed-by: lancea, redestad Contributed-by: eirbjo@gmail.com, claes.redestad@oracle.com
author redestad
date Fri, 17 Apr 2020 11:46:59 +0200
parents 55c4283a7606
children 61e0a7db5bfa
files src/java.base/share/classes/java/util/jar/JarFile.java src/java.base/share/classes/java/util/zip/ZipFile.java src/java.base/share/classes/jdk/internal/access/JavaUtilZipFileAccess.java
diffstat 3 files changed, 123 insertions(+), 30 deletions(-) [+]
line wrap: on
line diff
--- a/src/java.base/share/classes/java/util/jar/JarFile.java	Fri Apr 17 08:39:41 2020 +0200
+++ b/src/java.base/share/classes/java/util/jar/JarFile.java	Fri Apr 17 11:46:59 2020 +0200
@@ -431,10 +431,6 @@
         return man;
     }
 
-    private String[] getMetaInfEntryNames() {
-        return JUZFA.getMetaInfEntryNames((ZipFile)this);
-    }
-
     /**
      * Returns the {@code JarEntry} for the given base entry name or
      * {@code null} if not found.
@@ -504,11 +500,15 @@
      * </div>
      */
     public ZipEntry getEntry(String name) {
-        JarFileEntry je = getEntry0(name);
         if (isMultiRelease()) {
-            return getVersionedEntry(name, je);
+            JarEntry je = getVersionedEntry(name, null);
+            if (je == null) {
+                je = getEntry0(name);
+            }
+            return je;
+        } else {
+            return getEntry0(name);
         }
-        return je;
     }
 
     /**
@@ -598,21 +598,29 @@
         return name;
     }
 
-    private JarEntry getVersionedEntry(String name, JarEntry je) {
-        if (BASE_VERSION_FEATURE < versionFeature) {
-            if (!name.startsWith(META_INF)) {
+    private JarEntry getVersionedEntry(String name, JarEntry defaultEntry) {
+        if (!name.startsWith(META_INF)) {
+            int[] versions = JUZFA.getMetaInfVersions(this);
+            if (BASE_VERSION_FEATURE < versionFeature && versions.length > 0) {
                 // search for versioned entry
-                int v = versionFeature;
-                while (v > BASE_VERSION_FEATURE) {
-                    JarFileEntry vje = getEntry0(META_INF_VERSIONS + v + "/" + name);
+                for (int i = versions.length - 1; i >= 0; i--) {
+                    int version = versions[i];
+                    // skip versions above versionFeature
+                    if (version > versionFeature) {
+                        continue;
+                    }
+                    // skip versions below base version
+                    if (version < BASE_VERSION_FEATURE) {
+                        break;
+                    }
+                    JarFileEntry vje = getEntry0(META_INF_VERSIONS + version + "/" + name);
                     if (vje != null) {
                         return vje.withBasename(name);
                     }
-                    v--;
                 }
             }
         }
-        return je;
+        return defaultEntry;
     }
 
     // placeholder for now
@@ -707,7 +715,7 @@
         }
 
         if (verify) {
-            String[] names = getMetaInfEntryNames();
+            String[] names = JUZFA.getMetaInfEntryNames(this);
             if (names != null) {
                 for (String nameLower : names) {
                     String name = nameLower.toUpperCase(Locale.ENGLISH);
@@ -738,7 +746,7 @@
 
         // Verify "META-INF/" entries...
         try {
-            String[] names = getMetaInfEntryNames();
+            String[] names = JUZFA.getMetaInfEntryNames(this);
             if (names != null) {
                 for (String name : names) {
                     String uname = name.toUpperCase(Locale.ENGLISH);
@@ -932,7 +940,7 @@
             if (manEntry == null) {
                 // If not found, then iterate through all the "META-INF/"
                 // entries to find a match.
-                String[] names = getMetaInfEntryNames();
+                String[] names = JUZFA.getMetaInfEntryNames(this);
                 if (names != null) {
                     for (String name : names) {
                         if (MANIFEST_NAME.equals(name.toUpperCase(Locale.ENGLISH))) {
@@ -1016,7 +1024,7 @@
                         byte[] lbuf = new byte[512];
                         Attributes attr = new Attributes();
                         attr.read(new Manifest.FastInputStream(
-                            new ByteArrayInputStream(b)), lbuf);
+                                new ByteArrayInputStream(b)), lbuf);
                         isMultiRelease = Boolean.parseBoolean(
                             attr.getValue(Attributes.Name.MULTI_RELEASE));
                     }
@@ -1068,7 +1076,7 @@
      */
     JarEntry newEntry(String name) {
         if (isMultiRelease()) {
-            JarEntry vje = getVersionedEntry(name, (JarEntry)null);
+            JarEntry vje = getVersionedEntry(name, null);
             if (vje != null) {
                 return vje;
             }
--- a/src/java.base/share/classes/java/util/zip/ZipFile.java	Fri Apr 17 08:39:41 2020 +0200
+++ b/src/java.base/share/classes/java/util/zip/ZipFile.java	Fri Apr 17 11:46:59 2020 +0200
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1995, 2019, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1995, 2020, 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
@@ -50,14 +50,15 @@
 import java.util.Set;
 import java.util.Spliterator;
 import java.util.Spliterators;
+import java.util.TreeSet;
 import java.util.WeakHashMap;
 import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.IntFunction;
 import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
 import java.util.stream.Stream;
 import java.util.stream.StreamSupport;
-import jdk.internal.access.JavaLangAccess;
 import jdk.internal.access.JavaUtilZipFileAccess;
 import jdk.internal.access.SharedSecrets;
 import jdk.internal.misc.VM;
@@ -1048,8 +1049,21 @@
         }
     }
 
+    /**
+     * Returns the versions for which there exists a non-directory
+     * entry that begin with "META-INF/versions/" (case ignored).
+     * This method is used in JarFile, via SharedSecrets, as an
+     * optimization when looking up potentially versioned entries.
+     * Returns an empty array if no versioned entries exist.
+     */
+    private int[] getMetaInfVersions() {
+        synchronized (this) {
+            ensureOpen();
+            return res.zsrc.metaVersions;
+        }
+    }
+
     private static boolean isWindows;
-    private static final JavaLangAccess JLA;
 
     static {
         SharedSecrets.setJavaUtilZipFileAccess(
@@ -1059,8 +1073,12 @@
                     return zip.res.zsrc.startsWithLoc;
                 }
                 @Override
-                public String[] getMetaInfEntryNames(ZipFile zip) {
-                    return zip.getMetaInfEntryNames();
+                public String[] getMetaInfEntryNames(JarFile jar) {
+                    return ((ZipFile)jar).getMetaInfEntryNames();
+                }
+                @Override
+                public int[] getMetaInfVersions(JarFile jar) {
+                    return ((ZipFile)jar).getMetaInfVersions();
                 }
                 @Override
                 public JarEntry getEntry(ZipFile zip, String name,
@@ -1083,11 +1101,14 @@
                 }
              }
         );
-        JLA = SharedSecrets.getJavaLangAccess();
         isWindows = VM.getSavedProperty("os.name").contains("Windows");
     }
 
     private static class Source {
+        // "META-INF/".length()
+        private static final int META_INF_LENGTH = 9;
+        private static final int[] EMPTY_META_VERSIONS = new int[0];
+
         private final Key key;               // the key in files
         private int refs = 1;
 
@@ -1097,6 +1118,7 @@
         private byte[] comment;              // zip file comment
                                              // list of meta entries in META-INF dir
         private int[] metanames;
+        private int[] metaVersions;          // list of unique versions found in META-INF/versions/
         private final boolean startsWithLoc; // true, if zip file starts with LOCSIG (usually true)
 
         // A Hashmap for all entries.
@@ -1237,6 +1259,7 @@
             entries = null;
             table = null;
             metanames = null;
+            metaVersions = EMPTY_META_VERSIONS;
         }
 
         private static final int BUF_SIZE = 8192;
@@ -1424,6 +1447,8 @@
 
             // list for all meta entries
             ArrayList<Integer> metanamesList = null;
+            // Set of all version numbers seen in META-INF/versions/
+            Set<Integer> metaVersionsSet = null;
 
             // Iterate through the entries in the central directory
             int i = 0;
@@ -1461,6 +1486,17 @@
                     if (metanamesList == null)
                         metanamesList = new ArrayList<>(4);
                     metanamesList.add(pos);
+
+                    // If this is a versioned entry, parse the version
+                    // and store it for later. This optimizes lookup
+                    // performance in multi-release jar files
+                    int version = getMetaVersion(cen,
+                        pos + CENHDR + META_INF_LENGTH, nlen - META_INF_LENGTH);
+                    if (version > 0) {
+                        if (metaVersionsSet == null)
+                            metaVersionsSet = new TreeSet<>();
+                        metaVersionsSet.add(version);
+                    }
                 }
                 // skip ext and comment
                 pos += (CENHDR + nlen + elen + clen);
@@ -1473,6 +1509,15 @@
                     metanames[j] = metanamesList.get(j);
                 }
             }
+            if (metaVersionsSet != null) {
+                metaVersions = new int[metaVersionsSet.size()];
+                int c = 0;
+                for (Integer version : metaVersionsSet) {
+                    metaVersions[c++] = version;
+                }
+            } else {
+                metaVersions = EMPTY_META_VERSIONS;
+            }
             if (pos + ENDHDR != cen.length) {
                 zerror("invalid CEN header (bad header size)");
             }
@@ -1550,7 +1595,7 @@
          */
         private static boolean isMetaName(byte[] name, int off, int len) {
             // Use the "oldest ASCII trick in the book"
-            return len > 9                     // "META-INF/".length()
+            return len > META_INF_LENGTH       // "META-INF/".length()
                 && name[off + len - 1] != '/'  // non-directory
                 && (name[off++] | 0x20) == 'm'
                 && (name[off++] | 0x20) == 'e'
@@ -1563,6 +1608,45 @@
                 && (name[off]         ) == '/';
         }
 
+        /*
+         * If the bytes represents a non-directory name beginning
+         * with "versions/", continuing with a positive integer,
+         * followed by a '/', then return that integer value.
+         * Otherwise, return 0
+         */
+        private static int getMetaVersion(byte[] name, int off, int len) {
+            int nend = off + len;
+            if (!(len > 10                         // "versions//".length()
+                    && name[off + len - 1] != '/'  // non-directory
+                    && (name[off++] | 0x20) == 'v'
+                    && (name[off++] | 0x20) == 'e'
+                    && (name[off++] | 0x20) == 'r'
+                    && (name[off++] | 0x20) == 's'
+                    && (name[off++] | 0x20) == 'i'
+                    && (name[off++] | 0x20) == 'o'
+                    && (name[off++] | 0x20) == 'n'
+                    && (name[off++] | 0x20) == 's'
+                    && (name[off++]         ) == '/')) {
+                return 0;
+            }
+            int version = 0;
+            while (off < nend) {
+                final byte c = name[off++];
+                if (c == '/') {
+                    return version;
+                }
+                if (c < '0' || c > '9') {
+                    return 0;
+                }
+                version = version * 10 + c - '0';
+                // Check for overflow and leading zeros
+                if (version <= 0) {
+                    return 0;
+                }
+            }
+            return 0;
+        }
+
         /**
          * Returns the number of CEN headers in a central directory.
          * Will not throw, even if the zip file is corrupt.
--- a/src/java.base/share/classes/jdk/internal/access/JavaUtilZipFileAccess.java	Fri Apr 17 08:39:41 2020 +0200
+++ b/src/java.base/share/classes/jdk/internal/access/JavaUtilZipFileAccess.java	Fri Apr 17 11:46:59 2020 +0200
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2013, 2020, 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
@@ -25,16 +25,17 @@
 
 package jdk.internal.access;
 
-import java.io.IOException;
 import java.util.Enumeration;
 import java.util.function.Function;
 import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
 import java.util.stream.Stream;
 import java.util.zip.ZipFile;
 
 public interface JavaUtilZipFileAccess {
     public boolean startsWithLocHeader(ZipFile zip);
-    public String[] getMetaInfEntryNames(ZipFile zip);
+    public String[] getMetaInfEntryNames(JarFile zip);
+    public int[] getMetaInfVersions(JarFile zip);
     public JarEntry getEntry(ZipFile zip, String name, Function<String, JarEntry> func);
     public Enumeration<JarEntry> entries(ZipFile zip, Function<String, JarEntry> func);
     public Stream<JarEntry> stream(ZipFile zip, Function<String, JarEntry> func);