Skip to content

Commit dbfd785

Browse files
author
Phillip Webb
committed
Merge branch 'gh-1119'
Improve fat JAR performance. See gh-1119
2 parents 378d38e + 20fb55e commit dbfd785

File tree

13 files changed

+311
-141
lines changed

13 files changed

+311
-141
lines changed

spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/JarLauncher.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,5 @@ protected void postProcessClassPathArchives(List<Archive> archives) throws Excep
4444
public static void main(String[] args) {
4545
new JarLauncher().launch(args);
4646
}
47+
4748
}

spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/JavaAgentDetector.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,5 @@ public interface JavaAgentDetector {
3232
* @param url The url to examine
3333
*/
3434
public boolean isJavaAgentJar(URL url);
35+
3536
}

spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/LaunchedURLClassLoader.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.util.Collections;
2626
import java.util.Enumeration;
2727

28+
import org.springframework.boot.loader.jar.Handler;
2829
import org.springframework.boot.loader.jar.JarFile;
2930

3031
/**
@@ -93,7 +94,6 @@ private boolean hasURLs() {
9394

9495
@Override
9596
public Enumeration<URL> getResources(String name) throws IOException {
96-
9797
if (this.rootClassLoader == null) {
9898
return findResources(name);
9999
}
@@ -116,6 +116,7 @@ public URL nextElement() {
116116
}
117117
return localResources.nextElement();
118118
}
119+
119120
};
120121
}
121122

@@ -128,7 +129,13 @@ protected Class<?> loadClass(String name, boolean resolve)
128129
synchronized (this) {
129130
Class<?> loadedClass = findLoadedClass(name);
130131
if (loadedClass == null) {
131-
loadedClass = doLoadClass(name);
132+
Handler.setUseFastConnectionExceptions(true);
133+
try {
134+
loadedClass = doLoadClass(name);
135+
}
136+
finally {
137+
Handler.setUseFastConnectionExceptions(false);
138+
}
132139
}
133140
if (resolve) {
134141
resolveClass(loadedClass);
@@ -214,4 +221,5 @@ public Object run() throws ClassNotFoundException {
214221
// Ignore
215222
}
216223
}
224+
217225
}

spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/WarLauncher.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,5 @@ public AsciiBytes apply(AsciiBytes entryName, Archive.Entry entry) {
7979
public static void main(String[] args) {
8080
new WarLauncher().launch(args);
8181
}
82+
8283
}

spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/data/RandomAccessData.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,5 +65,7 @@ public static enum ResourceAccess {
6565
* Obtain access to the underlying resource on each read, releasing it when done.
6666
*/
6767
PER_READ
68+
6869
}
70+
6971
}

spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/data/RandomAccessDataFile.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@ private long moveOn(int amount) {
221221
this.position += amount;
222222
return amount;
223223
}
224+
224225
}
225226

226227
/**
@@ -277,5 +278,7 @@ public void close() throws IOException {
277278
throw new IOException(ex);
278279
}
279280
}
281+
280282
}
283+
281284
}

spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/Bytes.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,4 +76,5 @@ public static long littleEndianValue(byte[] bytes, int offset, int length) {
7676
}
7777
return value;
7878
}
79+
7980
}

spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/CentralDirectoryEndRecord.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,4 +119,5 @@ public RandomAccessData getCentralDirectory(RandomAccessData data) {
119119
public int getNumberOfRecords() {
120120
return (int) Bytes.littleEndianValue(this.block, this.offset + 10, 2);
121121
}
122+
122123
}

spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/Handler.java

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,14 @@
1818

1919
import java.io.File;
2020
import java.io.IOException;
21+
import java.lang.ref.SoftReference;
2122
import java.lang.reflect.Method;
2223
import java.net.MalformedURLException;
2324
import java.net.URL;
2425
import java.net.URLConnection;
2526
import java.net.URLStreamHandler;
27+
import java.util.Map;
28+
import java.util.concurrent.ConcurrentHashMap;
2629
import java.util.logging.Level;
2730
import java.util.logging.Logger;
2831

@@ -39,7 +42,7 @@ public class Handler extends URLStreamHandler {
3942

4043
private static final String FILE_PROTOCOL = "file:";
4144

42-
private static final String SEPARATOR = JarURLConnection.SEPARATOR;
45+
private static final String SEPARATOR = "!/";
4346

4447
private static final String[] FALLBACK_HANDLERS = { "sun.net.www.protocol.jar.Handler" };
4548

@@ -55,6 +58,11 @@ public class Handler extends URLStreamHandler {
5558
OPEN_CONNECTION_METHOD = method;
5659
}
5760

61+
private static SoftReference<Map<File, JarFile>> rootFileCache;
62+
static {
63+
rootFileCache = new SoftReference<Map<File, JarFile>>(null);
64+
}
65+
5866
private final Logger logger = Logger.getLogger(getClass().getName());
5967

6068
private final JarFile jarFile;
@@ -153,7 +161,14 @@ private JarFile getRootJarFile(String name) throws IOException {
153161
throw new IllegalStateException("Not a file URL");
154162
}
155163
String path = name.substring(FILE_PROTOCOL.length());
156-
return new JarFile(new File(path));
164+
File file = new File(path);
165+
Map<File, JarFile> cache = rootFileCache.get();
166+
JarFile jarFile = (cache == null ? null : cache.get(file));
167+
if (jarFile == null) {
168+
jarFile = new JarFile(file);
169+
addToRootFileCache(file, jarFile);
170+
}
171+
return jarFile;
157172
}
158173
catch (Exception ex) {
159174
throw new IOException("Unable to open root Jar file '" + name + "'", ex);
@@ -168,4 +183,29 @@ private JarFile getNestedJarFile(JarFile jarFile, String name) throws IOExceptio
168183
}
169184
return jarFile.getNestedJarFile(jarEntry);
170185
}
186+
187+
/**
188+
* Add the given {@link JarFile} to the root file cache.
189+
* @param sourceFile the source file to add
190+
* @param jarFile the jar file.
191+
*/
192+
static void addToRootFileCache(File sourceFile, JarFile jarFile) {
193+
Map<File, JarFile> cache = rootFileCache.get();
194+
if (cache == null) {
195+
cache = new ConcurrentHashMap<File, JarFile>();
196+
rootFileCache = new SoftReference<Map<File, JarFile>>(cache);
197+
}
198+
cache.put(sourceFile, jarFile);
199+
}
200+
201+
/**
202+
* Set if a generic static exception can be thrown when a URL cannot be connected.
203+
* This optimization is used during class loading to save creating lots of exceptions
204+
* which are then swallowed.
205+
* @param useFastConnectionExceptions if fast connection exceptions can be used.
206+
*/
207+
public static void setUseFastConnectionExceptions(boolean useFastConnectionExceptions) {
208+
JarURLConnection.setUseFastExceptions(useFastConnectionExceptions);
209+
}
210+
171211
}

spring-boot-tools/spring-boot-loader/src/main/java/org/springframework/boot/loader/jar/JarEntryData.java

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,22 +53,30 @@ public final class JarEntryData {
5353

5454
private SoftReference<JarEntry> entry;
5555

56+
JarFile nestedJar;
57+
5658
public JarEntryData(JarFile source, byte[] header, InputStream inputStream)
5759
throws IOException {
58-
5960
this.source = source;
6061
this.header = header;
6162
long nameLength = Bytes.littleEndianValue(header, 28, 2);
6263
long extraLength = Bytes.littleEndianValue(header, 30, 2);
6364
long commentLength = Bytes.littleEndianValue(header, 32, 2);
64-
6565
this.name = new AsciiBytes(Bytes.get(inputStream, nameLength));
6666
this.extra = Bytes.get(inputStream, extraLength);
6767
this.comment = new AsciiBytes(Bytes.get(inputStream, commentLength));
68-
6968
this.localHeaderOffset = Bytes.littleEndianValue(header, 42, 4);
7069
}
7170

71+
private JarEntryData(JarEntryData master, JarFile source, AsciiBytes name) {
72+
this.header = master.header;
73+
this.extra = master.extra;
74+
this.comment = master.comment;
75+
this.localHeaderOffset = master.localHeaderOffset;
76+
this.source = source;
77+
this.name = name;
78+
}
79+
7280
void setName(AsciiBytes name) {
7381
this.name = name;
7482
}
@@ -154,6 +162,10 @@ public AsciiBytes getComment() {
154162
return this.comment;
155163
}
156164

165+
JarEntryData createFilteredCopy(JarFile jarFile, AsciiBytes name) {
166+
return new JarEntryData(this, jarFile, name);
167+
}
168+
157169
/**
158170
* Create a new {@link JarEntryData} instance from the specified input stream.
159171
* @param source the source {@link JarFile}

0 commit comments

Comments
 (0)