Skip to content

Commit

Permalink
Reduce memory footprint
Browse files Browse the repository at this point in the history
This commit introduces various optimizations that allow to significantly
reduce Spring Boot applications footprint when compiled as native images.

The first one is the capability to remove XML parsers (see related
oracle/graal#2327 GraalVM issue) including those configured habitually
by Spring. XML Parsers from Logback, Tomcat, Spring Boot, Spring Framework
Core/WebMvc/WebFlux can be disabled via -Dspring.graal.remove-xml-support=true
and is implemented via various substitutions.
Notice for now FormHttpMessageConverter is used instead of
AllEncompassingFormHttpMessageConverter due to oracle/graal#2458.

It is now also possible to remove SpEL via -Dspring.graal.remove-spel-support=true
and JMX via -Dspring.graal.remove-jmx-support=true.

BackgroundPreinitializer are now disabled since they do not make sense with
GraalVM native.

spring-graal-feature is now added as a regular Maven dependency in order to make
the new org.springframework.boot.NativePropertiesListener taken in account when
the agent is used.

Key samples and petclinic ones have been updated to take advantage of those
optimizations. Spring Fu samples now also takes advantages of
spring-projects-experimental/spring-fu#269 and those capabilities.

In the long run, these "not so easy to maintain substitutions" could maybe be
replaced by a new GraalVM capability that would monitor used classes by the
JVM via an agent and pass this list to native-image compiler to remove those
unused classes from the native image in order to load classes lazily like
the JVM does. It would also have the advantage to not require an explicit
option to enable those optimizations.

Closes spring-atticgh-109
  • Loading branch information
sdeleuze committed May 15, 2020
1 parent 179a1e5 commit 19c2e3e
Show file tree
Hide file tree
Showing 108 changed files with 1,736 additions and 755 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ dependency-reduced-pom.xml
.vscode
.factorypath
log.txt
target
target/
.gradle/
build/
.classpath
.project
unpack
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.springframework.boot;

import java.util.Properties;

import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.PropertiesPropertySource;

public class NativePropertiesListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {
ConfigurableEnvironment environment = event.getEnvironment();
Properties props = new Properties();
props.put("server.servlet.register-default-servlet", "false");
environment.getPropertySources().addFirst(new PropertiesPropertySource("myProps", props));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
*/
package org.springframework.boot.autoconfigure.web.reactive;

import reactor.netty.DisposableServer;

import org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar;
import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext;
import org.springframework.boot.web.server.WebServerFactoryCustomizerBeanPostProcessor;
Expand All @@ -28,42 +30,7 @@
import org.springframework.http.codec.support.DefaultServerCodecConfigurer;
import org.springframework.web.reactive.HandlerResult;

import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelInboundHandler;
import io.netty.channel.ChannelInboundHandlerAdapter;
import reactor.netty.DisposableServer;

@NativeImageHint(trigger=WebFluxAutoConfiguration.class,typeInfos = {
// These two believed through WebFluxConfigurationSupport, CodecConfigurer.properties
// TODO this feels wrong, a lot of entries here are just super types of some core set of base types (e.g. ServerBootstrapAcceptor)
// we should be able to add all those supertypes (e.g. if methods being exposed maybe that is the trigger to add supers)
// This TypeInfo for vanilla-jpa sample
@TypeInfo(types= {ChannelInboundHandlerAdapter.class,ChannelHandlerAdapter.class,
ChannelHandler.class,ChannelInboundHandler.class},
typeNames = {
"io.netty.channel.ChannelInitializer",
"io.netty.channel.DefaultChannelPipeline$HeadContext",
"io.netty.channel.DefaultChannelPipeline$TailContext",
"reactor.netty.channel.BootstrapHandlers$BootstrapInitializerHandler",
"io.netty.channel.ChannelDuplexHandler",
"io.netty.channel.CombinedChannelDuplexHandler",
"io.netty.channel.AbstractChannelHandlerContext",
"io.netty.channel.ChannelHandlerContext",
"io.netty.handler.codec.http.HttpServerCodec",
"reactor.netty.channel.ChannelOperationsHandler",
"reactor.netty.http.server.HttpTrafficHandler",
"io.netty.channel.ChannelDuplexHandler",
"io.netty.channel.ChannelFutureListener",
"io.netty.channel.DefaultChannelPipeline",
"io.netty.channel.ChannelPipeline",
"io.netty.channel.ChannelInboundInvoker",
"io.netty.channel.ChannelOutboundInvoker",
"io.netty.channel.ChannelHandler",
"io.netty.util.concurrent.GenericFutureListener",
"io.netty.bootstrap.ServerBootstrap$1",
"io.netty.bootstrap.ServerBootstrap$ServerBootstrapAcceptor"},
access=AccessBits.CLASS|AccessBits.DECLARED_CONSTRUCTORS|AccessBits.DECLARED_METHODS),
@TypeInfo(types= {DefaultClientCodecConfigurer.class,DefaultServerCodecConfigurer.class,
ClientCodecConfigurer.class, ServerCodecConfigurer.class, // TODO Also put in regular web auto config?
HandlerResult.class,
Expand All @@ -72,18 +39,9 @@
// TODO Aren't these also needed for non reactive auto configuration web? Is there a common configuration supertype between those
// configurations that they can be hung off
typeNames= {
// {
// // DelegatingWebFluxConfiguration (used by webfluxconfigurationsupport)
// "name": "com.sun.xml.internal.stream.XMLInputFactoryImpl",
// "allDeclaredConstructors": true,
// "allDeclaredMethods": true
// }
"com.sun.xml.internal.stream.XMLInputFactoryImpl",
"com.fasterxml.jackson.databind.ObjectMapper",
"com.fasterxml.jackson.core.JsonGenerator",
"com.fasterxml.jackson.dataformat.smile.SmileFactory",
"javax.xml.bind.Binder",

"com.fasterxml.jackson.dataformat.smile.SmileFactory",
"com.google.protobuf.Message",
"org.synchronoss.cloud.nio.multipart.NioMultipartParser"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright 2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.autoconfigure.web.reactive.netty;

import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelInboundHandler;
import io.netty.channel.ChannelInboundHandlerAdapter;
import reactor.netty.DisposableServer;

import org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration;
import org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar;
import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration;
import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext;
import org.springframework.boot.web.server.WebServerFactoryCustomizerBeanPostProcessor;
import org.springframework.graal.extension.NativeImageConfiguration;
import org.springframework.graal.extension.NativeImageHint;
import org.springframework.graal.extension.TypeInfo;
import org.springframework.graal.type.AccessBits;
import org.springframework.http.codec.ClientCodecConfigurer;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.http.codec.support.DefaultClientCodecConfigurer;
import org.springframework.http.codec.support.DefaultServerCodecConfigurer;
import org.springframework.web.reactive.HandlerResult;

@NativeImageHint(typeInfos = {
@TypeInfo(types= {ChannelInboundHandlerAdapter.class,ChannelHandlerAdapter.class,
ChannelHandler.class,ChannelInboundHandler.class},
typeNames = {
"io.netty.channel.ChannelInitializer",
"io.netty.channel.DefaultChannelPipeline$HeadContext",
"io.netty.channel.DefaultChannelPipeline$TailContext",
"reactor.netty.channel.BootstrapHandlers$BootstrapInitializerHandler",
"io.netty.channel.ChannelDuplexHandler",
"io.netty.channel.CombinedChannelDuplexHandler",
"io.netty.channel.AbstractChannelHandlerContext",
"io.netty.channel.ChannelHandlerContext",
"io.netty.handler.codec.http.HttpServerCodec",
"reactor.netty.channel.ChannelOperationsHandler",
"reactor.netty.http.server.HttpTrafficHandler",
"io.netty.channel.ChannelDuplexHandler",
"io.netty.channel.ChannelFutureListener",
"io.netty.channel.DefaultChannelPipeline",
"io.netty.channel.ChannelPipeline",
"io.netty.channel.ChannelInboundInvoker",
"io.netty.channel.ChannelOutboundInvoker",
"io.netty.channel.ChannelHandler",
"io.netty.util.concurrent.GenericFutureListener",
"io.netty.bootstrap.ServerBootstrap$1",
"io.netty.bootstrap.ServerBootstrap$ServerBootstrapAcceptor",
"java.lang.management.ManagementFactory",
"java.lang.management.RuntimeMXBean"},
access=AccessBits.CLASS|AccessBits.DECLARED_CONSTRUCTORS|AccessBits.DECLARED_METHODS)
})
public class Hints implements NativeImageConfiguration {
}
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@
HttpRequestHandlerAdapter.class,SimpleControllerHandlerAdapter.class,RequestMappingHandlerAdapter.class,
HandlerFunctionAdapter.class,ExceptionHandlerExceptionResolver.class,ResponseStatusExceptionResolver.class,
DefaultHandlerExceptionResolver.class,DefaultRequestToViewNameTranslator.class,InternalResourceViewResolver.class,SessionFlashMapManager.class,
AuthConfigFactoryImpl.class,
// vvv these are pulled in due to actuator testing but probably necessary for general app support
MatchableHandlerMapping.class,SimpleUrlHandlerMapping.class,HandlerMappingIntrospector.class,
AbstractDetectingUrlHandlerMapping.class,AbstractHandlerMapping.class,AbstractHandlerMethodMapping.class,
Expand All @@ -115,8 +114,7 @@
AbstractConfigurableWebServerFactory.class,
DispatcherServletPath.class,AbstractErrorController.class,
AbstractServletWebServerFactory.class,AbstractFilterRegistrationBean.class
}),
@TypeInfo(types= {DefaultServlet.class},access=AccessBits.CLASS|AccessBits.DECLARED_CONSTRUCTORS|AccessBits.DECLARED_METHODS)
})
})
@NativeImageHint(trigger=WebMvcAutoConfiguration.class, typeInfos = {
@TypeInfo(types= {AnnotationConfigServletWebServerApplicationContext.class,
Expand All @@ -126,19 +124,7 @@
ProtocolHandler.class,AbstractProtocol.class,AbstractHttp11Protocol.class,AbstractHttp11JsseProtocol.class,Http11NioProtocol.class,
ErrorPage.class,DefaultErrorViewResolver.class,BeanNameViewResolver.class,
ErrorPageRegistrarBeanPostProcessor.class},
typeNames= {"org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$EmptyHandler",
//TODO I wonder if this one should be conditional on jax2b being on the classpath, this is the stacktrace from securing-web
//Caused by: javax.xml.transform.TransformerFactoryConfigurationError: Provider com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl not found
// at javax.xml.transform.FactoryFinder.newInstance(FactoryFinder.java:181) ~[na:na]
// at javax.xml.transform.FactoryFinder.find(FactoryFinder.java:261) ~[na:na]
// at javax.xml.transform.TransformerFactory.newInstance(TransformerFactory.java:106) ~[na:na]
// at org.springframework.http.converter.xml.AbstractXmlHttpMessageConverter.<init>(AbstractXmlHttpMessageConverter.java:52) ~[na:na]
// at org.springframework.http.converter.xml.AbstractJaxb2HttpMessageConverter.<init>(AbstractJaxb2HttpMessageConverter.java:38) ~[na:na]
// at org.springframework.http.converter.xml.Jaxb2RootElementHttpMessageConverter.<init>(Jaxb2RootElementHttpMessageConverter.java:64) ~[na:na]
// at org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter.<init>(AllEncompassingFormHttpMessageConverter.java:72) ~[na:na]
// at org.springframework.web.filter.FormContentFilter.<init>(FormContentFilter.java:61) ~[securing-web:na]
"com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl"
}),
typeNames= {"org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$EmptyHandler"}),
@TypeInfo(types= {Callable.class},access=AccessBits.CLASS|AccessBits.DECLARED_METHODS|AccessBits.DECLARED_CONSTRUCTORS)},abortIfTypesMissing = true)
// TODO this is an interesting one as it is hinted at by both flavours of BeanPostProcessorsRegistrar (reactive and servlet)
@NativeImageHint(trigger=BeanPostProcessorsRegistrar.class,typeInfos= {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ org.springframework.boot.autoconfigure.validation.Hints
org.springframework.boot.autoconfigure.websocket.servlet.Hints
org.springframework.boot.autoconfigure.web.reactive.Hints
org.springframework.boot.autoconfigure.web.reactive.function.client.Hints
org.springframework.boot.autoconfigure.web.reactive.netty.Hints
org.springframework.boot.autoconfigure.web.servlet.Hints
org.springframework.boot.context.properties.Hints
org.springframework.boot.context.Hints
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
org.springframework.context.ApplicationListener=org.springframework.boot.NativePropertiesListener
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,31 @@
{"class": "org.springframework.transaction.annotation.Propagation"},
{"class": "org.springframework.util.unit.DataUnit"},
{"class": "org.springframework.util.unit.DataSize"},
// To avoid XML parsers to be included
{"class": "org.springframework.messaging.simp.config.AbstractMessageBrokerConfiguration" },
{"class": "org.springframework.http.MediaType" },
{"class": "org.springframework.http.codec.support.BaseDefaultCodecs" },
{"class": "org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter" },
{"class": "org.springframework.util.ClassUtils" },
{"class": "org.springframework.util.ConcurrentReferenceHashMap" },
{"class": "org.springframework.util.CollectionUtils" },
{"class": "org.springframework.util.LinkedCaseInsensitiveMap" },
{"class": "org.springframework.util.MimeType" },
{"class": "org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport" },
{"class": "org.springframework.web.socket.sockjs.transport.TransportHandlingSockJsService" },
{"class": "org.springframework.web.client.RestTemplate" },
{"class": "org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter" },
{"class": "org.springframework.beans.factory.xml.XmlBeanDefinitionReader" },
{"class": "org.springframework.util.ReflectionUtils" },

// Spring Boot
{"class": "org.springframework.boot.CommandLineRunner"},
{"class": "org.springframework.boot.validation.MessageInterpolatorFactory"},

// Tomcat, to avoid XML parsers and crypto classes
{"class": "org.apache.catalina.servlets.DefaultServlet" },
{"class": "org.apache.catalina.Globals" },

// Logging
{"package": "ch.qos.logback.core"},
{"package": "ch.qos.logback.classic"},
Expand Down
6 changes: 6 additions & 0 deletions spring-graal-native-docs/src/main/asciidoc/options.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@ catch those other places you missed.

* `-Dspring.graal.remove-yaml-support=true` removes Yaml support from Spring Boot, enabling faster compilation and smaller executables.

* `-Dspring.graal.remove-xml-support=true` removes XML support from Spring Boot, enabling faster compilation and smaller executables.

* `-Dspring.graal.remove-spel-support=true` removes SpEL support from Spring Boot, enabling faster compilation and smaller executables.

* `-Dspring.graal.remove-jmx-support=true` removes JMX support from Spring Boot, enabling faster compilation and smaller executables.

* `-Dspring.graal.dump-config=/tmp/dump.txt` dumps the configuration to the specified file.

* `-Dspring.graal.missing-selector-hints=warning` switches the feature from a hard error for missing hints to a warning.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ public abstract class ConfigOptions {
private final static boolean REMOVE_UNUSED_AUTOCONFIG;

private final static boolean REMOVE_YAML_SUPPORT;

private final static boolean REMOVE_XML_SUPPORT;

private final static boolean REMOVE_SPEL_SUPPORT;

private final static boolean REMOVE_JMX_SUPPORT;

private final static String DUMP_CONFIG;

Expand Down Expand Up @@ -78,7 +84,19 @@ enum Mode {
}
REMOVE_YAML_SUPPORT = Boolean.valueOf(System.getProperty("spring.graal.remove-yaml-support", "false"));
if (REMOVE_YAML_SUPPORT) {
System.out.println("Skipping Yaml support");
System.out.println("Removing Yaml support");
}
REMOVE_XML_SUPPORT = Boolean.valueOf(System.getProperty("spring.graal.remove-xml-support", "false"));
if (REMOVE_XML_SUPPORT) {
System.out.println("Removing XML support");
}
REMOVE_SPEL_SUPPORT = Boolean.valueOf(System.getProperty("spring.graal.remove-spel-support", "false"));
if (REMOVE_SPEL_SUPPORT) {
System.out.println("Removing SpEL support");
}
REMOVE_JMX_SUPPORT = Boolean.valueOf(System.getProperty("spring.graal.remove-jmx-support", "false"));
if (REMOVE_JMX_SUPPORT) {
System.out.println("Removing JMX support");
}
DUMP_CONFIG = System.getProperty("spring.graal.dump-config");
if (DUMP_CONFIG!=null) {
Expand Down Expand Up @@ -110,6 +128,18 @@ public static boolean shouldRemoveYamlSupport() {
return REMOVE_YAML_SUPPORT;
}

public static boolean shouldRemoveXmlSupport() {
return REMOVE_XML_SUPPORT;
}

public static boolean shouldRemoveSpelSupport() {
return REMOVE_SPEL_SUPPORT;
}

public static boolean shouldRemoveJmxSupport() {
return REMOVE_JMX_SUPPORT;
}

public static boolean isAgentMode() {
return MODE==Mode.AGENT;
}
Expand Down
Loading

0 comments on commit 19c2e3e

Please sign in to comment.