Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Investigate Spring Web Services performance when using a fat jar #3640

Closed
wilkinsona opened this issue Aug 1, 2015 · 2 comments
Closed

Investigate Spring Web Services performance when using a fat jar #3640

wilkinsona opened this issue Aug 1, 2015 · 2 comments
Assignees
Milestone

Comments

@wilkinsona
Copy link
Member

Via this question on Stack Overflow, java -jar is reportedly noticeably slower than mvn spring-boot:run

@wilkinsona wilkinsona self-assigned this Aug 3, 2015
@wilkinsona
Copy link
Member Author

My own testing with JMeter does show a performance difference, but nothing like that which is reported in the question. java -jar provides roughly 87% of the throughput of ./gradlew bootRun. YourKit tells me that much of the overhead comes from Boot's JarURLConnection which is only used when running from a nested jar.

Someone's very kindly done some analysis in an answer on StackOverflow. The problem stems from a very inefficient SAAJ implementation in the JRE:

What this means is that for every request, the SAAJ implementation in the JRE requests a new TransformerFactory and a new DocumentBuilderFactory. These factories are located using the JDK 1.3 service provider discovery mechanism, which involves searching for certain resources under META-INF/services. The performance of that search is very sensitive to the characteristics of the class loaders from which these resources are looked up. That is why you see a difference between mvn spring-boot:run and using an executable JAR. In particular, the executable JAR contains embedded JARs and looking up resources from these embedded JARs is expensive. For mvn spring-boot:run this is not the case, which explains why it is faster.

Finding the services entails calling getResources on LaunchedURLClassLoader and then iterating over the returned enumeration calling hasMoreElements and nextElement. The performance of hasMoreElements can be improved by enabling fast exceptions (throwing a generic FileNotFoundException rather than creating a new one each time) while it's being called. With this change in place java -jar provides roughly 92% of the throughput of ./gradle bootRun.

@wilkinsona wilkinsona added this to the 1.2.6 milestone Aug 3, 2015
wilkinsona added a commit that referenced this issue Aug 3, 2015
When nested jars are being used, hasMoreElements requires opening a
connection for an entry in every nested jar. If that entry doesn't
exist, a FileNotFoundException is thrown to indicate that a particular
jar doesn't contain the requested entry. This exception is used to
indicate the lack of an entry and is then swallowed, i.e. its stack
trace is of no importance. This means that the performance of
hasMoreElements can be improved by switching on fast exceptions while
it's being called. When fast exceptions are switched on a general
purpose pre-initialized FileNotFoundException is thrown rather than
creating a new FileNotFoundException instance each time.

In certain situations, the use of fast exceptions as described above
can improve performance fairly significantly. The JRE's default SAAJ
implementation uses META-INF/services-based discovery for _every_
request that's handled by Spring Web Services. Each discovery attempt
results in hasMoreElements being called making its performance
critical to throughput.

See gh-3640
@philwebb
Copy link
Member

philwebb commented Aug 4, 2015

👍 Nice, that exception trick solved similar issues in 1.x

tsachev added a commit to tsachev/spring-boot that referenced this issue Nov 23, 2015
Some libraries like aspectj are using findResource to see the raw bytecode of a class.
It will even call findResource for every method of every class of beans that are post processed.
This can be significant performance hit on startup when LaunchedURLClassLoader and there are a lot of nested jars.

Related spring-projectsgh-3640
Fix spring-projectsgh-4557
snicoll pushed a commit that referenced this issue Nov 30, 2015
Some libraries like aspectj are using findResource to see the raw
bytecode of a class. It will even call findResource for every method of
every class of beans that are post processed. This can be significant
performance hit on startup when LaunchedURLClassLoader and there are a
lot of nested jars.

See gh-3640
Fixes gh-4557
wilkinsona pushed a commit that referenced this issue Dec 3, 2015
Some libraries like aspectj are using findResource to see the raw
bytecode of a class. It will even call findResource for every method of
every class of beans that are post processed. This can be significant
performance hit on startup when LaunchedURLClassLoader and there are a
lot of nested jars.

See gh-3640
Fixes gh-4557
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants