Bootstrap page payload remains in ThreadLocal cache managed by JSoup
See original GitHub issueHello! I got a memory leak. I will describe it below as detailed as possible, and also attach a demo project to reproduce it on your machine.
Vaadin 14.0.4, Spring Boot 2.1.7, Java 11.0.4, Tomcat/Jetty/Undertow, macOS High Sierra + Safari.
Let’s take my-starter-project
as a base.
application.properties:
vaadin.heartbeatInterval=5
server.servlet.session.timeout=60
MainView.java:
private static String generateRandomString(java.util.Random rnd, int length){
// Return some string of size `length`
...
}
@Route
@UIScope
@Push(transport = Transport.LONG_POLLING)
public class MainView extends VerticalLayout {
...
public MainView(@Autowired MessageBean bean) {
Button button = new Button("Click me",
e -> {
Random rnd = new Random();
for (int i = 0; i < 50; i++) {
Div div = new Div();
div.setText(generateRandomString(rnd, 1024 * 10));
this.add(div);
}
Notification.show(bean.getMessage());
});
add(button);
}
The above code outputs to browser window 50 lines of 10Kb each, no issues. Now, let’s replace the constructor’s code.
public MainView(@Autowired MessageBean bean) {
Random rnd = new Random();
for (int i = 0; i < 50; i++) {
Div div = new Div();
div.setText(generateRandomString(rnd, 1024 * 10));
this.add(div);
}
}
The above code has an issue: a memory leak – several objects org.apache.tomcat.utils.threads.TaskThread
are never destroyed (even after MainView objects count and SpringVaadinSession objects count become 0 due to session expiration). It is not related to servlet container, I tried all three available in Spring Boot: Tomcat, Jetty and Undertow. I attached a screenshot. It contains a string with Push data of AtmospherePushConnection (start with <!doctype html>...
).
In constructor I pass a lot of data to user’s browser window (a large log file, instead of random string data as in this sample), so this memory leak steals a lot of memory at each page refresh and finally leads to OutOfMemory heap error.
Issue Analytics
- State:
- Created 4 years ago
- Comments:5 (2 by maintainers)
Top GitHub Comments
Based on my investigation, this isn’t a regular memory leak per se. Instead, this is a per-thread cache managed by JSoup that may under specific end up with quite much content. What it means is that memory use doesn’t grow without bounds, but instead only based on the number of request-serving threads used by the application server.
JSoup caches instances of
StringBuilder
to reduce the number of instances that would have to be garbage collected. The cache works so that there is one instance per Java thread, managed using aThreadLocal
. This is handled in the methodorg.jsoup.helper.StringUtil.stringBuilder()
.Vaadin uses JSoup to build the bootstrap html page contents. Since this is the last use of JSoup in a typical Vaadin request, the full bootstrap page will remain in a cached string builder until the next time some JSoup functionality that uses a string builder is run on the same thread.
The caching was reimplemented in https://github.com/jhy/jsoup/issues/1059 because of issues similar to this and the fix was included in JSoup version 1.12.1. Vaadin 14 is using version 1.11.3.
I haven’t done a full test run with the new JSoup version, but at least the basic stuff seems to work and the reported issue can no longer be observed if I update
pom.xml
to explicitly use the newer version:If we for some reason cannot update Vaadin to use the newer JSoup version, then we could as a workaround add a dummy call to
StringUtil.stringBuilder()
right after building the bootstrap html string in the end ofBootstrapHandler.synchronizedHandleRequest
andWebComponentBootstrapHandler.writeBootstrapPage
. This would cause the previous contents of the cached string builder to be cleared so that memory can be reclaimed immediately.@Legioth Thank you for informing me about your release plans!