SpringBoot Application Startup Analysis and Optimization in NetEase Cloud Music
The article details how NetEase Cloud Music’s SpringBoot scaffolding suffered minute‑long startup times, how the team pinpointed bottlenecks such as Scala property parsing, RPC builder/service initialization and thousands of beans using log‑based timing and Arthas profiling, and how moving parsing to a Maven plugin, making RPC connections asynchronous, and applying custom lazy‑initialization cut startup time by roughly 40%.
SpringBoot has become the preferred way of developing Java applications, and it is widely used in NetEase Cloud Music. To reduce the cost of pulling new projects, a scaffolding template is used for project initialization. However, as business iterates, the startup time of applications based on this scaffolding becomes very slow, severely affecting development efficiency and increasing the risk of financial loss when restarting clusters for production issues.
This article presents a detailed analysis of the startup process of the scaffolding application and proposes optimization strategies that are generally applicable to SpringBoot applications.
Project Background
Some Cloud Music services take more than 2 minutes to start, with large projects like iplay-server taking up to 10 minutes. This slows down development and testing cycles and can cause significant financial loss during production restarts. The main challenge is to locate optimization points in a complex application that integrates many components while keeping the optimizations transparent to business code.
Capabilities Provided by the Scaffolding
The scaffolding is a Maven archetype built on SpringBoot, offering unified dependency management, middleware starters, lifecycle management, and configuration parsing.
Overall Startup Process
The startup flow mirrors the standard SpringBoot startup, including environment creation, props file parsing (using Scala), ApplicationContext initialization, bean definition loading, context refresh, health checks, and component registration.
Startup Time Analysis Phase
Using log points and the SpringApplicationRunListener extension, the team measured the duration of each startup stage. The core implementation is shown below:
public class LifecycleAnalysisSpringApplicationRunListener implements SpringApplicationRunListener {
private long originStartTime;
@Override
public void starting() {
long now = System.currentTimeMillis();
originStartTime = now;
}
@Override
public void finished(ConfigurableApplicationContext context, Throwable exception) {
long now = System.currentTimeMillis();
DefaultTimeAnalysis.getInstance().logCost(getApplicationName() + ": 容器启动完成耗时", now - originStartTime);
}
}The analysis identified several costly areas:
Scala parsing of props files.
Initialization of various bean categories, especially RpcBuilder and RpcService .
Unclassified beans ("OTHER") that number over 2,900.
Bean categories were defined as follows:
public enum BeanClassifierEnums {
RPC_BUILDER,
RPC_SERVICE,
NYDUS,
REDIS,
MC,
SQL_MANAGER,
SQL_IMPL,
OTHER;
}To pinpoint the exact slow logic, the team used Arthas profiler to generate flame graphs. By attaching a debugger (JDWP) with -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=10000 , they started the JVM in a blocked state, connected Arthas, and recorded wall‑time events:
profiler -e wall startAfter the application started, they stopped profiling and saved the flame graph:
profiler stop --file /tmp/server.htmlThe flame graph revealed that the most time‑consuming stack frames in RpcService initialization were HTTP requests to the configuration center.
Optimization Implementation
1. Props File Parsing : Moved parsing to a Maven plugin during the compile phase, generating Scala files ahead of time.
2. RpcBuilder and RpcService : Made network connection establishment asynchronous using a dedicated thread pool and a CountDownLatch‑like mechanism to wait for completion before health checks.
3. Lazy Loading : Enabled lazy initialization for non‑configuration beans in non‑production environments. Since SpringBoot 1.x lacks the spring.main.lazy-initialization property, a custom BeanDefinitionRegistryPostProcessor was created:
public class LazyInitPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
if (!isTestEnv() && !isDevEnv()) {
return;
}
for (String name : registry.getBeanDefinitionNames()) {
BeanDefinition beanDefinition = registry.getBeanDefinition(name);
String beanClassName = beanDefinition.getBeanClassName();
if (null != beanClassName) {
try {
Class
beanClazz = Class.forName(beanClassName);
Configuration annotation = AnnotationUtils.findAnnotation(beanClazz, Configuration.class);
if (null != annotation) {
continue;
}
} catch (ClassNotFoundException e) {
log.warn("class not found,class -> {}", beanClassName);
}
}
registry.getBeanDefinition(name).setLazyInit(true);
}
}
}
public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
/**
* Set whether this bean should be lazily initialized.
*/
void setLazyInit(boolean lazyInit);
}These changes reduced the total startup time in the development environment from 452 seconds to 276 seconds, a roughly 40% improvement.
Conclusion
The article outlines a systematic approach to analyzing and optimizing SpringBoot startup performance, offering reusable techniques such as log‑based timing, profiler‑driven investigation, asynchronous initialization, and lazy loading. It also suggests that alternative frameworks like Micronaut can achieve even faster startup times.
NetEase Cloud Music Tech Team
Official account of NetEase Cloud Music Tech Team
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.