-
Notifications
You must be signed in to change notification settings - Fork 41.4k
Description
Affects: Spring Boot 2.1.0
We are precompiling our JSP files on application startup and we are getting an OutOfMemoryError when migrating our application from Spring Boot 2.0.4 to 2.1.0.
java.lang.OutOfMemoryError: Requested array size exceeds VM limit
at java.util.ArrayList.<init>(ArrayList.java:153) ~[na:1.8.0_202]
at org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedContext$$Lambda$349/790229674.apply(Unknown Source) ~[na:na]
at java.util.Map.computeIfAbsent(Map.java:957) ~[na:1.8.0_202]
at org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedContext.getLoadOnStartupWrappers(TomcatEmbeddedContext.java:75) ~[spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]
at org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedContext.lambda$deferredLoadOnStartup$0(TomcatEmbeddedContext.java:65) ~[spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]
at org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedContext$$Lambda$348/215632153.run(Unknown Source) ~[na:na]
at org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedContext.doWithThreadContextClassLoader(TomcatEmbeddedContext.java:109) ~[spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]
at org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedContext.deferredLoadOnStartup(TomcatEmbeddedContext.java:64) ~[spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]
at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.performDeferredLoadOnStartup(TomcatWebServer.java:282) ~[spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]
at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.start(TomcatWebServer.java:200) ~[spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.startWebServer(ServletWebServerApplicationContext.java:300) ~[spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.finishRefresh(ServletWebServerApplicationContext.java:162) ~[spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:549) ~[spring-context-5.1.2.RELEASE.jar:5.1.2.RELEASE]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:140) ~[spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:775) [spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397) [spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:316) [spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1260) [spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1248) [spring-boot-2.1.0.RELEASE.jar:2.1.0.RELEASE]
at com.example.demo.DemoApplication.main(DemoApplication.java:19) [classes/:na]
We do the precompilation by creating a servlet for each of our JSP files like this:
@Bean
public ServletContextInitializer preCompileJspsAtStartup() {
return servletContext -> {
foreachJSP(jspPath -> {
ServletRegistration.Dynamic reg = servletContext.addServlet(jspPath, Constants.JSP_SERVLET_CLASS);
reg.addMapping(jspPath);
});
};
}
Here servletContext.addServlet
creates a new StandardWrapper for each of the files. This Wrapper sets the loadOnStartup value to -1
by default but returns Integer.MAX_VALUE
for JSP servlets.
@Override
public int getLoadOnStartup() {
if (isJspServlet && loadOnStartup < 0) {
/*
* JspServlet must always be preloaded, because its instance is
* used during registerJMX (when registering the JSP
* monitoring mbean)
*/
return Integer.MAX_VALUE;
} else {
return this.loadOnStartup;
}
}
The error is happening on TomcatEmbeddedContext because it is trying to create an ArrayList with the size of order
(which in case of a JSP is Integer.MAX_VALUE
).
73: int order = wrapper.getLoadOnStartup();
74: if (order >= 0) {
// next line is calling new ArrayList<>(order) instead of new ArrayList<>()
75: grouped.computeIfAbsent(order, ArrayList::new);
76: grouped.get(order).add(wrapper);
77: }
You can avoid the error by simple explicitly setting the loadOnStartup
value to a positive integer.
@Bean
public ServletContextInitializer preCompileJspsAtStartup() {
return servletContext -> {
foreachJSP(jspPath -> {
ServletRegistration.Dynamic reg = servletContext.addServlet(jspPath, Constants.JSP_SERVLET_CLASS);
reg.addMapping(jspPath);
reg.setLoadOnStartup(99); // manually set to avoid problems
});
};
}
You can replicate the error by running this simple application: https://github.com/jagobagascon/Spring-Boot-OutOfMemoryError-Bug