Skip to content

Commit 157dc23

Browse files
committed
Reduce allocations due to ClassLoader#defineClassSourceLocation
It will call URL#toExternalForm() for each class, which will generate again and again the same string pointing to the jar files. By introducing a specific URLStreamHandler, we are able to avoid calling URL#toExternalForm() and use a cached version of it for each JarResource. While not making a tremendous difference, it is an easy way to reduce our allocations at startup.
1 parent 2ce3255 commit 157dc23

File tree

1 file changed

+75
-1
lines changed
  • independent-projects/bootstrap/runner/src/main/java/io/quarkus/bootstrap/runner

1 file changed

+75
-1
lines changed

independent-projects/bootstrap/runner/src/main/java/io/quarkus/bootstrap/runner/JarResource.java

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
package io.quarkus.bootstrap.runner;
22

3+
import java.io.File;
4+
import java.io.FileInputStream;
5+
import java.io.FileNotFoundException;
36
import java.io.IOException;
47
import java.io.InputStream;
58
import java.net.MalformedURLException;
69
import java.net.URI;
710
import java.net.URISyntaxException;
811
import java.net.URL;
12+
import java.net.URLConnection;
13+
import java.net.URLStreamHandler;
914
import java.nio.file.Path;
1015
import java.security.CodeSource;
1116
import java.security.ProtectionDomain;
@@ -44,7 +49,7 @@ public void init() {
4449
path = '/' + path;
4550
}
4651
URI uri = new URI("file", null, path, null);
47-
url = uri.toURL();
52+
url = new URL((URL) null, uri.toString(), new JarUrlStreamHandler(uri));
4853
} catch (URISyntaxException | MalformedURLException e) {
4954
throw new RuntimeException("Unable to create protection domain for " + jarPath, e);
5055
}
@@ -201,4 +206,73 @@ public boolean equals(Object o) {
201206
public int hashCode() {
202207
return Objects.hashCode(jarPath);
203208
}
209+
210+
/**
211+
* This URLStreamHandler is designed to handle only one jar, which is the one passed in the constructor.
212+
* The goal here is to cache the external form of the URL.
213+
* <p>
214+
* Do not use this class outside of this extremely specific purpose.
215+
*/
216+
private static class JarUrlStreamHandler extends URLStreamHandler {
217+
218+
private final String externalForm;
219+
220+
private JarUrlStreamHandler(URI uri) {
221+
this.externalForm = "file:".concat(uri.getRawPath());
222+
// while it would be more optimized to store the URI here for when we open connections
223+
// opening a connection for ProtectionDomains is actually extremely rare
224+
// and never done in production at runtime so we favored reducing memory allocations for the common case
225+
}
226+
227+
@Override
228+
protected URLConnection openConnection(URL u) throws IOException {
229+
return new JarURLConnection(u);
230+
}
231+
232+
@Override
233+
protected String toExternalForm(URL u) {
234+
return externalForm;
235+
}
236+
}
237+
238+
private static class JarURLConnection extends URLConnection {
239+
240+
private final File file;
241+
242+
private JarURLConnection(URL url) throws IOException {
243+
super(url);
244+
try {
245+
this.file = new File(url.toURI());
246+
} catch (URISyntaxException e) {
247+
throw new IOException(e);
248+
}
249+
}
250+
251+
@Override
252+
public void connect() throws IOException {
253+
if (!file.exists()) {
254+
throw new FileNotFoundException(file.getAbsolutePath());
255+
}
256+
}
257+
258+
@Override
259+
public InputStream getInputStream() throws IOException {
260+
return new FileInputStream(file);
261+
}
262+
263+
@Override
264+
public int getContentLength() {
265+
return (int) file.length();
266+
}
267+
268+
@Override
269+
public long getContentLengthLong() {
270+
return file.length();
271+
}
272+
273+
@Override
274+
public String getContentType() {
275+
return "application/java-archive";
276+
}
277+
}
204278
}

0 commit comments

Comments
 (0)