Performance regression in BuiltinClassLoader?

Eirik Bjørsnøs eirbjo at gmail.com
Mon Jan 18 18:37:13 UTC 2021


For reference, here's the EnterpriseClassloader benchmark I made.

It assumes you have the jar files from Spring Petclinic in BOOT-INF/lib
(but any large collection of jars should work).

public class ZIPBenchmark {

    public static void main(String[] args) {

        final File libDir = new File("BOOT-INF/lib/");

        final Strategy strategy = Strategy.valueOf(args[0]);

        EnterpriseClassLoader classLoader =
EnterpriseClassLoader.create(libDir, strategy);

        final List<String> names = classLoader.getNames();


        long gcTimeBefore = 0;
        long gcCountBefore = 0;
        for (GarbageCollectorMXBean bean :
ManagementFactory.getGarbageCollectorMXBeans()) {
            gcCountBefore += bean.getCollectionCount();
            gcTimeBefore += bean.getCollectionTime();
        }


        long before = System.nanoTime();



        for (String name : names) {
            classLoader.getResource(name);
        }

        long gcTimeAfter  = 0;
        long gcCountAfter = 0;
        for (GarbageCollectorMXBean bean :
ManagementFactory.getGarbageCollectorMXBeans()) {
            gcCountAfter += bean.getCollectionCount();
            gcTimeAfter += bean.getCollectionTime();
        }

        final long time = System.nanoTime() - before;

        System.out.printf("%s: took %s ms\n", strategy,
TimeUnit.NANOSECONDS.toMillis(time));
        System.out.printf("Collections: %s\n", gcCountAfter-gcCountBefore);
        System.out.printf("Collection time: %s ms\n",
gcTimeAfter-gcTimeBefore);

    }



    public enum Strategy {
        SELF_ONLY, BOOTSTRAP, PLATFORM, SYSTEM, PARENT_DELEGATION
    }


    private static class EnterpriseClassLoader extends URLClassLoader {
        private final List<String> names;

        private final Strategy lookupStrategy;

        public EnterpriseClassLoader(URL[] urls, List<String> names,
Strategy strategy) {
            super(urls, strategy == Strategy.BOOTSTRAP ? null :
ClassLoader.getSystemClassLoader());
            this.names = names;
            this.lookupStrategy = strategy;
        }

        public static EnterpriseClassLoader create(File libDir, Strategy
strategy) {
            final File[] jarFiles = libDir.listFiles(f ->
f.getName().endsWith(".jar"));

            final URL[] urls = Stream.of(jarFiles)
                    .map(File::toURI).map(EnterpriseClassLoader::toURL)
                    .toArray(URL[]::new);

            List<String> names = Stream.of(jarFiles)
                    .flatMap(EnterpriseClassLoader::entryNameStream)
                    .collect(Collectors.toList());

            return new EnterpriseClassLoader(urls, names, strategy);
        }

        @Override
        public URL getResource(String name) {
            if(lookupStrategy == Strategy.BOOTSTRAP) {
                return super.getResource(name);
            } else if (lookupStrategy == Strategy.PLATFORM) {
                return
ClassLoader.getPlatformClassLoader().getResource(name);
            } else if (lookupStrategy == Strategy.SYSTEM) {
                return ClassLoader.getSystemClassLoader().getResource(name);
            } else if(lookupStrategy == Strategy.PARENT_DELEGATION) {
                return super.getResource(name);
            } else if(lookupStrategy == Strategy.SELF_ONLY) {
                return super.findResource(name);
            } else {
                throw new IllegalStateException("Unknown strategy");
            }
        }

        @Override
        public URL findResource(String name) {
            if(lookupStrategy == Strategy.BOOTSTRAP) {
                return null;
            } else {
                return super.findResource(name);
            }
        }

        private static Stream<String> entryNameStream(File url) {
            List<String> names;
            try (JarFile file = new JarFile(url)) {

                names = file.stream()
                        .map(ZipEntry::getName)
                        .filter(name -> name.endsWith(".class"))
                        .collect(Collectors.toList());

            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            return names.stream();
        }

        private static URL toURL(URI uri) {
            try {
                return uri.toURL();
            } catch (MalformedURLException e) {
                throw new RuntimeException(e);
            }
        }
        public List<String> getNames() {
            return names;
        }

    }


}

On Mon, Jan 18, 2021 at 7:34 PM Eirik Bjørsnøs <eirbjo at gmail.com> wrote:

>
> Hello,
>
> I've been looking into ZipFile again with the aim to speed up typical
> enterprise classloading / resource lookups.
>
> To test the performance impact of my changes, I made a benchmark which
> creates an EnterpriseClassLoader (subclass of URLClassLoader) with the jar
> files in Spring Petclinic (89 jars with ~30k entries total).
>
> (This is intentionally implemented using a plain main method with no JMH
> or warmup since I want to measure a representative cold application startup)
>
> The benchmark measures the time needed to call getResource 30K times, once
> for each entry name.
>
> Using 15.0.1, this completes in ~1700 ms
> Using 17 master, it completes in 12851 ms
>
> If I run the benchmark from the classpath instead of as a module, the
> result is even worse:
>
> 15: 1724 ms
> 17: 21885 ms
>
> If also measure GC collections and times, and get the following:
>
> 15:
> Collections: 4
> Collection time: 12 ms
>
> 17:
> Collections: 24
> Collection time: 44 ms
>
> (However, a JHM with -prof gc indicates that both 17 and 15 allocate
> ~5.6Kb/getResource after warmup, so not convinced this needs to be
> allocation related)
>
> The EnterpriseClassLoader is a bit clever in that it can perform lookups
> using different strategies: Regular parent-first, self-only, or use the
> bootstrap / platform system classloaders directly.
>
> Here are the results on 17 using each strategy (benchmark on class path):
>
> PARENT_DELEGATION: 23111 ms
> SELF_ONLY: 428 ms
> BOOTSTRAP: 8131 ms
> PLATFORM: 15149 ms
> SYSTEM: 20628 ms
>
> Here are the same results on 15:
>
> PARENT_DELEGATION: 1691 ms
> SELF_ONLY: 393 ms
> BOOTSTRAP: 579 ms
> PLATFORM: 908 ms
> SYSTEM: 1368 ms
>
> Note that the delegation chain is Enterprise -> System -> Platform ->
> Bootstrap.
>
> Interesting to note that URLClassLoader itself does not seem to regress
> much when only looking up its own resources, while delegating up the chain
> does.
>
> Has there been any significant work in class loading / resource lookup
> since 15.0.1 that could help explain this regression?
>
> Eirik.
>
>
>


More information about the core-libs-dev mailing list