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.
3.7 KiB
07 — Failure modes
← prev 06 — The classpath-scan tax · next → 08 — What actually helps
The buffer truncates silently
BufferingApplicationStartup takes a capacity in its only constructor. Every guide picks
2048. Nothing tells you what happens when the application produces more steps than that —
so here it is, with 5,000 extra @Component classes on the classpath
(docs/output/06-buffer-overflow.txt):
--- capacity 2048 ---
started, recorded steps: 2048
--- capacity 16384 ---
started, recorded steps: 5400
The endpoint returns 200 with a well-formed answer. grep -ic buffer app.log returns
0. There is no warning and no exception.
It is worse than losing a suffix. Steps are recorded when they end, so the buffer keeps the first ones to finish — the innermost leaves — and drops the enclosing ones:
first 3: ['spring.boot.application.starting',
'spring.boot.application.environment-prepared',
'spring.boot.application.context-prepared']
last 3 : ['spring.beans.instantiate', ...]
last tags: [{'key': 'beanName', 'value': 'bulk2718'}]
spring.context.refresh, spring.boot.application.started and
spring.boot.application.ready are absent from the truncated timeline. Any self-time
calculation is now missing its outer frame, and any "the whole refresh took N" figure is
simply not in the data.
The capacity is the number of steps, not beans, and this application records slightly more than one step per bean. 16384 costs a few megabytes that are freed on the first drain. Pick the large number.
aFullBufferTruncatesSilentlyAndLosesTheOuterSteps in
StartupTimelineContractTests
pins this behaviour.
POST destroys the recording
Covered in chapter 02, repeated here because it is the
most expensive one: POST /actuator/startup drains. The second call returns an empty
timeline, and so does every subsequent GET. If you are following a guide that pipes the
POST into jq and the jq expression is wrong, restart the application — the data is
gone.
Injecting the concrete type
public StartupDiagnosticsEndpoint(BufferingApplicationStartup startup) { ... }
compiles, works in every test, and stops the application from starting the first time
somebody runs it without -Dstartup.tracking=buffering, because the registered singleton
is then a DefaultApplicationStartup. Inject the interface, narrow with instanceof, and
answer honestly when tracking is off.
Killing the app by its main class
Not a Spring problem, but it cost a run while producing this repository. pkill -f StartupDiagnosisApplication also matches the shell that is running the script that contains
that string, and kills it. scripts/stop.sh matches on the executable
being java and the jar name:
ps -eo pid=,comm=,args= | awk '$2 == "java" && /startup-diagnosis-1\.0\.0\.jar/ { print $1 }'
Trusting a single run
Startup timings on a shared or containerised machine move by several hundred milliseconds
between runs of the same jar — see the per-run columns in
docs/output/05-what-helps.txt, where the same configuration
produced 6.456 s and 7.473 s. Take a median of four or more, and treat any difference under
about 10% as noise. Every number in this repository that matters is a ratio between two
such medians, not a single measurement.
← prev 06 — The classpath-scan tax · next → 08 — What actually helps