Spring Boot startup time: bean-by-bean diagnosis, and one directory per post
Adds spring-boot-startup-time/, the companion project for BLOG-618: a runnable Spring Boot 4.1.1 application on JDK 25 that installs BufferingApplicationStartup and FlightRecorderApplicationStartup behind a system property, and a /diag/startup endpoint that computes step self time -- the number /actuator/startup does not give you and the one that names the actual culprits. Captured under docs/output/: the step tree sorted both ways, the same startup as JFR events, a +5000-class experiment putting 0.11 ms per scanned class on the classpath scan tax, the silent truncation a 2048-step buffer performs, and JDK 25 AOT cache timings (6.93 s to 4.82 s). Post body and metadata live in post/. Moves the existing Actuator project into actuator-in-production/ so the repository holds one directory per article; the root README is now an index.
This commit is contained in:
87
spring-boot-startup-time/docs/03-the-four-phases.md
Normal file
87
spring-boot-startup-time/docs/03-the-four-phases.md
Normal file
@@ -0,0 +1,87 @@
|
||||
# 03 — The four phases hiding inside one number
|
||||
|
||||
← prev [02 — Turning instrumentation on](02-turning-instrumentation-on.md) · next → [04 — Reading the step tree](04-reading-the-step-tree.md)
|
||||
|
||||
---
|
||||
|
||||
Grouping every recorded step by name and summing **self time** (see
|
||||
[chapter 04](04-reading-the-step-tree.md) for why self and not total) gives the shape of a
|
||||
startup. From [`docs/output/02-startup-tree.txt`](output/02-startup-tree.txt):
|
||||
|
||||
```
|
||||
self ms count step
|
||||
4253.68 361 spring.beans.instantiate
|
||||
1014.51 1 spring.context.config-classes.parse
|
||||
402.81 1 spring.boot.application.environment-prepared
|
||||
160.39 1 spring.boot.webserver.create
|
||||
153.07 1 spring.boot.application.started
|
||||
113.74 1 spring.context.refresh
|
||||
95.72 1 spring.data.repository.proxy
|
||||
51.50 6 spring.beans.smart-initialize
|
||||
43.85 1 spring.data.repository.scanning
|
||||
```
|
||||
|
||||
Four phases account for essentially all of it, and they respond to entirely different
|
||||
fixes.
|
||||
|
||||
## 1. Bean instantiation — `spring.beans.instantiate`
|
||||
|
||||
361 steps, 4.25 s. This is constructors, `@PostConstruct`, `FactoryBean.getObject()` and
|
||||
proxy creation. It is where your own code lives, and it is the only phase you can fix by
|
||||
changing application code.
|
||||
|
||||
Note what is *not* a separate step: `@PostConstruct` has no step of its own, so a cache
|
||||
warm shows up inside the owning bean's `spring.beans.instantiate`. `tariffCacheWarmer` in
|
||||
this repository spends all of its 504 ms in `@PostConstruct` and none in its constructor,
|
||||
and the timeline cannot tell you which.
|
||||
|
||||
## 2. Configuration class parsing — `spring.context.config-classes.parse`
|
||||
|
||||
One step, 1.01 s, and under JFR it carries a tag naming the cost driver:
|
||||
|
||||
```
|
||||
PT1.176668418S spring.context.config-classes.parse classCount=130,
|
||||
```
|
||||
|
||||
130 configuration classes. This step is component scanning plus `@Conditional` evaluation
|
||||
across every auto-configuration your classpath drags in. It is proportional to *classes
|
||||
inspected*, not to beans created — see [chapter 06](06-the-classpath-scan-tax.md), where
|
||||
adding 5,000 classes that are not beans at all still adds half a second here.
|
||||
|
||||
## 3. Environment preparation — `spring.boot.application.environment-prepared`
|
||||
|
||||
403 ms before any bean exists. Property sources, profile resolution, config data imports.
|
||||
Cheap to ignore and impossible to tune from application code, but it explains why "the app
|
||||
does nothing for the first half second".
|
||||
|
||||
## 4. Everything else
|
||||
|
||||
`spring.boot.webserver.create` (160 ms), the Spring Data repository steps (~150 ms across
|
||||
five step names), `spring.beans.smart-initialize`. Individually small; collectively a
|
||||
second. Spring Data's contribution scales with the number of repository *interfaces* and
|
||||
derived query methods, since each one is parsed and proxied.
|
||||
|
||||
## The step names, in the order they nest
|
||||
|
||||
```
|
||||
spring.boot.application.starting
|
||||
spring.boot.application.environment-prepared
|
||||
spring.boot.application.context-prepared
|
||||
spring.context.refresh
|
||||
├── spring.context.beandef-registry.post-process
|
||||
│ └── spring.context.config-classes.parse ← scanning + conditions
|
||||
├── spring.context.bean-factory.post-process
|
||||
├── spring.context.beans.post-process
|
||||
│ └── spring.beans.instantiate (×N, nested by dependency)
|
||||
└── spring.boot.webserver.create
|
||||
spring.boot.application.started
|
||||
spring.boot.application.ready
|
||||
```
|
||||
|
||||
A step is recorded **when it ends**, which is why the outermost ones appear last in the
|
||||
timeline — and why they are the first casualties of a full buffer
|
||||
([chapter 07](07-failure-modes.md)).
|
||||
|
||||
---
|
||||
|
||||
← prev [02 — Turning instrumentation on](02-turning-instrumentation-on.md) · next → [04 — Reading the step tree](04-reading-the-step-tree.md)
|
||||
Reference in New Issue
Block a user