From 49146f93ff5a9e306df0d4c5b236b1a33c0caec6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=9A=E4=B9=8B?= Date: Tue, 1 Mar 2022 16:43:28 +0800 Subject: [PATCH 1/3] Add a cache to LaunchedURLClassloader to improve startup performance This commit adds a ClassLoaderCache to LaunchedURLClassLoader to improve startup performance. URLClassLoader shows awful performance when find Classes/Resources which do not exist. --- .../boot/loader/ClassLoaderCache.java | 120 ++++++++++++++++++ .../boot/loader/LaunchedURLClassLoader.java | 36 +++++- .../loader/LaunchedURLClassLoaderTests.java | 50 ++++++++ 3 files changed, 202 insertions(+), 4 deletions(-) create mode 100644 spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ClassLoaderCache.java diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ClassLoaderCache.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ClassLoaderCache.java new file mode 100644 index 000000000000..11943de1324d --- /dev/null +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ClassLoaderCache.java @@ -0,0 +1,120 @@ +/* + * Copyright 2012-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.loader; + +import java.net.URL; +import java.util.Collections; +import java.util.Enumeration; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Optional; + +/** + * A class/resource cache used by {@link LaunchedURLClassLoader} . + * + * @author Bingjie Lv + * @since 2.7.0 + * + */ +public class ClassLoaderCache { + + private boolean enableCache = Boolean.getBoolean("loader.cache.enable"); + + private final int cacheSize = Integer.getInteger("loader.cache.size", 3000); + + private Map classNotFoundExceptionCache; + + private Map> resourceUrlCache; + + private Map>> resourcesUrlCache; + + public ClassLoaderCache() { + this.classNotFoundExceptionCache = createCache(this.cacheSize); + this.resourceUrlCache = createCache(this.cacheSize); + this.resourcesUrlCache = createCache(this.cacheSize); + } + + public void fastClassNotFoundException(String name) throws ClassNotFoundException { + if (!this.enableCache) { + return; + } + ClassNotFoundException classNotFoundException = this.classNotFoundExceptionCache.get(name); + if (classNotFoundException != null) { + throw classNotFoundException; + } + } + + public void cacheClassNotFoundException(String name, ClassNotFoundException exception) { + if (!this.enableCache) { + return; + } + this.classNotFoundExceptionCache.put(name, exception); + } + + public Optional getResourceCache(String name) { + if (!this.enableCache) { + return null; + } + return this.resourceUrlCache.get(name); + } + + public URL cacheResourceUrl(String name, URL url) { + if (!this.enableCache) { + return url; + } + this.resourceUrlCache.put(name, (url != null) ? Optional.of(url) : Optional.empty()); + return url; + } + + public Optional> getResourcesCache(String name) { + if (!this.enableCache) { + return null; + } + return this.resourcesUrlCache.get(name); + } + + public Enumeration cacheResourceUrls(String name, Enumeration urlEnumeration) { + if (!this.enableCache) { + return urlEnumeration; + } + if (!urlEnumeration.hasMoreElements()) { + this.resourcesUrlCache.put(name, Optional.of(urlEnumeration)); + } + return urlEnumeration; + } + + public void clearCache() { + if (this.enableCache) { + this.classNotFoundExceptionCache.clear(); + this.resourceUrlCache.clear(); + this.resourcesUrlCache.clear(); + } + } + + public void setEnableCache(boolean enableCache) { + this.enableCache = enableCache; + } + + protected Map createCache(int maxSize) { + return Collections.synchronizedMap(new LinkedHashMap(maxSize, 0.75f, true) { + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return size() >= maxSize; + } + }); + } +} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/LaunchedURLClassLoader.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/LaunchedURLClassLoader.java index 75ac50815094..3e9d33dbb098 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/LaunchedURLClassLoader.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/LaunchedURLClassLoader.java @@ -26,6 +26,7 @@ import java.security.AccessController; import java.security.PrivilegedExceptionAction; import java.util.Enumeration; +import java.util.Optional; import java.util.function.Supplier; import java.util.jar.JarFile; import java.util.jar.Manifest; @@ -55,6 +56,8 @@ public class LaunchedURLClassLoader extends URLClassLoader { private final Object packageLock = new Object(); + private final ClassLoaderCache loaderCache = new ClassLoaderCache(); + private volatile DefinePackageCallType definePackageCallType; /** @@ -92,12 +95,16 @@ public LaunchedURLClassLoader(boolean exploded, Archive rootArchive, URL[] urls, @Override public URL findResource(String name) { + Optional optional = this.loaderCache.getResourceCache(name); + if (optional != null) { + return optional.orElse(null); + } if (this.exploded) { - return super.findResource(name); + return this.loaderCache.cacheResourceUrl(name, super.findResource(name)); } Handler.setUseFastConnectionExceptions(true); try { - return super.findResource(name); + return this.loaderCache.cacheResourceUrl(name, super.findResource(name)); } finally { Handler.setUseFastConnectionExceptions(false); @@ -106,12 +113,17 @@ public URL findResource(String name) { @Override public Enumeration findResources(String name) throws IOException { + Optional> optional = this.loaderCache.getResourcesCache(name); + if (optional != null) { + return optional.orElse(null); + } if (this.exploded) { - return super.findResources(name); + return this.loaderCache.cacheResourceUrls(name, super.findResources(name)); } Handler.setUseFastConnectionExceptions(true); try { - return new UseFastConnectionExceptionsEnumeration(super.findResources(name)); + return this.loaderCache.cacheResourceUrls(name, + new UseFastConnectionExceptionsEnumeration(super.findResources(name))); } finally { Handler.setUseFastConnectionExceptions(false); @@ -120,6 +132,21 @@ public Enumeration findResources(String name) throws IOException { @Override protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + this.loaderCache.fastClassNotFoundException(name); + try { + return loadClassInternal(name, resolve); + } + catch (ClassNotFoundException ex) { + this.loaderCache.cacheClassNotFoundException(name, ex); + throw ex; + } + } + + public void setEnableCache(boolean enableCache){ + loaderCache.setEnableCache(enableCache); + } + + private Class loadClassInternal(String name, boolean resolve) throws ClassNotFoundException { if (name.startsWith("org.springframework.boot.loader.jarmode.")) { try { Class result = loadClassInLaunchedClassLoader(name); @@ -297,6 +324,7 @@ private T doDefinePackage(DefinePackageCallType type, Supplier call) { * Clear URL caches. */ public void clearCache() { + this.loaderCache.clearCache(); if (this.exploded) { return; } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/LaunchedURLClassLoaderTests.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/LaunchedURLClassLoaderTests.java index c37e01de61c3..226aea054aa1 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/LaunchedURLClassLoaderTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/LaunchedURLClassLoaderTests.java @@ -108,4 +108,54 @@ void resolveFromNestedWhileThreadIsInterrupted() throws Exception { } } + @Test + void enableLoaderCache() throws Exception { + resolveResourceFromArchive(); + resolveResourcesFromArchive(); + resolveRootPathFromArchive(); + resolveRootResourcesFromArchive(); + resolveFromNested(); + resolveFromNestedWhileThreadIsInterrupted(); + + LaunchedURLClassLoader loader = new LaunchedURLClassLoader( + new URL[] { new URL("jar:file:src/test/resources/jars/app.jar!/") }, getClass().getClassLoader()); + loader.setEnableCache(true); + assertThat(loader.getResource("demo/Application.java")).isEqualTo(loader.getResource("demo/Application.java")); + assertThat(loader.loadClass("demo.Application")).isEqualTo(loader.loadClass("demo.Application")); + assertThat(loader.getResource("demo/ApplicationNotExist.java")).isNull(); + assertThat(loader.getResources("demo/ApplicationNotExist.java").hasMoreElements()).isNotEqualTo(true); + assertThat(loader.getResources("demo/ApplicationNotExist.java").hasMoreElements()).isNotEqualTo(true); + ClassNotFoundException ex = null; + ClassNotFoundException ex1 = null; + ClassNotFoundException ex2 = null; + ClassNotFoundException ex3 = null; + try { + loader.loadClass("demo.ApplicationNotExist"); + } catch (ClassNotFoundException exception){ + ex = exception; + } + try { + loader.loadClass("demo.ApplicationNotExist"); + } catch (ClassNotFoundException exception){ + ex1 = exception; + } + try { + loader.setEnableCache(false); + loader.loadClass("demo.ApplicationNotExist"); + } catch (ClassNotFoundException exception){ + ex2 = exception; + loader.setEnableCache(true); + } + try { + loader.clearCache(); + loader.loadClass("demo.ApplicationNotExist"); + } catch (ClassNotFoundException exception){ + ex3 = exception; + } + assertThat(ex).isNotNull(); + assertThat(ex1).isNotNull().isEqualTo(ex); + assertThat(ex2).isNotNull().isNotEqualTo(ex).isNotEqualTo(ex1); + assertThat(ex3).isNotNull().isNotEqualTo(ex2).isNotEqualTo(ex1).isNotEqualTo(ex); + } + } From 22d2d2917b86958dfd75873adcc8d2ec060f285f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=9A=E4=B9=8B?= Date: Tue, 1 Mar 2022 19:23:59 +0800 Subject: [PATCH 2/3] Add a cache to LaunchedURLClassloader to improve startup performance This commit adds a ClassLoaderCache to LaunchedURLClassLoader to improve startup performance. URLClassLoader shows awful performance when find Classes/Resources which do not exist. --- .../boot/loader/LaunchedURLClassLoader.java | 4 ++-- .../boot/loader/LaunchedURLClassLoaderTests.java | 12 ++++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/LaunchedURLClassLoader.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/LaunchedURLClassLoader.java index 3e9d33dbb098..06084906c304 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/LaunchedURLClassLoader.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/LaunchedURLClassLoader.java @@ -142,8 +142,8 @@ protected Class loadClass(String name, boolean resolve) throws ClassNotFoundE } } - public void setEnableCache(boolean enableCache){ - loaderCache.setEnableCache(enableCache); + public void setEnableCache(boolean enableCache) { + this.loaderCache.setEnableCache(enableCache); } private Class loadClassInternal(String name, boolean resolve) throws ClassNotFoundException { diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/LaunchedURLClassLoaderTests.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/LaunchedURLClassLoaderTests.java index 226aea054aa1..a40956a1683f 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/LaunchedURLClassLoaderTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/test/java/org/springframework/boot/loader/LaunchedURLClassLoaderTests.java @@ -131,25 +131,29 @@ void enableLoaderCache() throws Exception { ClassNotFoundException ex3 = null; try { loader.loadClass("demo.ApplicationNotExist"); - } catch (ClassNotFoundException exception){ + } + catch (ClassNotFoundException exception) { ex = exception; } try { loader.loadClass("demo.ApplicationNotExist"); - } catch (ClassNotFoundException exception){ + } + catch (ClassNotFoundException exception) { ex1 = exception; } try { loader.setEnableCache(false); loader.loadClass("demo.ApplicationNotExist"); - } catch (ClassNotFoundException exception){ + } + catch (ClassNotFoundException exception) { ex2 = exception; loader.setEnableCache(true); } try { loader.clearCache(); loader.loadClass("demo.ApplicationNotExist"); - } catch (ClassNotFoundException exception){ + } + catch (ClassNotFoundException exception) { ex3 = exception; } assertThat(ex).isNotNull(); From d3a871738ad69d336d5a1d81f52402d7bae72c4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=9A=E4=B9=8B?= Date: Tue, 1 Mar 2022 20:33:45 +0800 Subject: [PATCH 3/3] Add a cache to LaunchedURLClassloader to improve startup performance This commit adds a ClassLoaderCache to LaunchedURLClassLoader to improve startup performance. URLClassLoader shows awful performance when find Classes/Resources which do not exist. --- .../java/org/springframework/boot/loader/ClassLoaderCache.java | 1 + 1 file changed, 1 insertion(+) diff --git a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ClassLoaderCache.java b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ClassLoaderCache.java index 11943de1324d..625b4455458d 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ClassLoaderCache.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/ClassLoaderCache.java @@ -117,4 +117,5 @@ protected boolean removeEldestEntry(Map.Entry eldest) { } }); } + }