JVM 後端的問題:功能無敵但太重
Spring Boot 是 JVM 生態的企業後端標準——Bean lifecycle、AOP、JPA、Spring Security、Spring Cloud,一套生態解決了幾乎所有問題。
但 Spring Boot 有一個老問題:冷啟動慢、記憶體用量高。
一個標準的 Spring Boot 應用啟動時要:掃描 classpath、解析所有 Bean 定義、建立 ApplicationContext、注入依賴。這一套流程讓 Spring Boot 應用通常要 3–10 秒才能 ready。
這在以前不是問題:一個 Spring 服務起來就不關了,3 秒只是一次性成本。但 serverless(AWS Lambda / Google Cloud Run)的環境是 cold start 直接影響 response latency,Kubernetes 的 rolling update 是每次部署都要重啟——這時候 Spring 的冷啟動就從「一次性成本」變成了「反覆出現的問題」。
| Framework | 冷啟動 | 記憶體(idle) | 語言 | 定位 |
|---|---|---|---|---|
| Spring Boot | 3–8 秒 | ~200–500 MB | Java / Kotlin | JVM 企業標準,生態最完整 |
| Micronaut | < 1 秒 | ~50–100 MB | Java / Kotlin / Groovy | Ahead-of-time DI,解 Spring 啟動問題 |
| Quarkus | < 1 秒(JVM)/ < 0.1 秒(native) | ~30–80 MB(native) | Java / Kotlin | GraalVM native image,serverless 最優 |
| Ktor | < 0.5 秒 | ~30–60 MB | Kotlin | Kotlin coroutine-first,minimal |
Spring Boot:功能最完整,但要付重量的代價
Spring Boot 的 DI / AOP / lifecycle 設計是在 runtime 做的——JVM 啟動後才掃描 classpath、解析 annotation、建立 proxy:
@Service
public class UserService {
@Autowired
private UserRepository userRepo; // ← JVM 啟動後才注入
@Transactional // ← AOP proxy,runtime 動態建立
public User createUser(UserCreateDto dto) {
return userRepo.save(new User(dto.name(), dto.email()));
}
}這種 runtime DI 讓 Spring 的 configuration 非常彈性(你可以在 runtime 動態替換 Bean),但代價是啟動時要做大量工作。
Spring Boot 的生態是它最強的護城河:Spring Security、Spring Data JPA、Spring Cloud(服務發現、circuit breaker、config center)、Spring Batch——這些東西在 Micronaut / Quarkus 上都有對應但成熟度不及。
適合:大型企業後端、需要 Spring 生態深度整合、team 對 Spring 有集體熟悉度的場景。
Micronaut:Ahead-of-Time DI 解啟動速度
Micronaut(2018)解的是 Spring 的啟動問題,方法是把 DI 的工作從 runtime 移到 compile time:
@Singleton
public class UserService {
private final UserRepository userRepo;
// 注入在 compile time 就確定了,不用 JVM 啟動時掃描
public UserService(UserRepository userRepo) {
this.userRepo = userRepo;
}
}Micronaut 的 annotation processor 在 compile 時就把依賴關係算好,生成 DI 代碼。JVM 啟動時不需要掃描、不需要建立 proxy,直接執行。這讓 Micronaut 的啟動時間降到不到 1 秒,記憶體用量也明顯小於 Spring。
缺點:Compile time DI 讓某些 Spring 的動態特性不支援(例如 runtime 動態替換 Bean 的場景)。生態比 Spring 小,某些 Spring 深度整合的功能沒有對應。
適合:想要 Spring-like DI + annotation 的寫法,但需要比 Spring 更低的啟動時間和記憶體用量。
Quarkus:GraalVM Native Image,為 Serverless 而生
Quarkus(2019)走得更激進:不只是 compile time DI,而是支援 GraalVM native image——把整個 JVM application 編譯成平台原生的可執行檔(類似 Go 的靜態 binary)。
JVM mode:java -jar quarkus-app.jar
→ 啟動 < 1 秒,記憶體 ~50 MB
Native mode:./quarkus-app(GraalVM native image)
→ 啟動 < 0.1 秒,記憶體 ~30 MB
→ 適合 AWS Lambda cold start、K8s HPA 快速 scale out
Quarkus 在 native 模式下是 serverless 場景最強的 JVM 選擇:
@Path("/users")
public class UserResource {
@Inject
UserService userService;
@GET
@Path("/{id}")
@Produces(MediaType.APPLICATION_JSON)
public Response show(@PathParam("id") Long id) {
return Response.ok(userService.findById(id)).build();
}
}缺點:Native image 的 build time 很長(幾分鐘),而且有些反射相關的 Java 特性在 native mode 需要額外設定。
適合:Serverless / FaaS 場景(cold start 是硬需求);Kubernetes 高頻率 rolling update 需要快速啟動。
Ktor:Kotlin Coroutine-First,不要 Spring 的另一條路
Ktor 是 JetBrains 官方維護的 Kotlin web framework,設計哲學是 Kotlin coroutine-first + minimal。
它的 routing DSL 利用 Kotlin 的特性讓程式碼很簡潔:
fun Application.configureRouting() {
routing {
get("/users/{id}") {
val id = call.parameters["id"]!!.toLong()
val user = userService.findById(id) // ← suspend function,non-blocking
call.respond(user)
}
post("/users") {
val dto = call.receive<UserCreateDto>() // ← 自動反序列化 + validation
val user = userService.create(dto)
call.respond(HttpStatusCode.Created, user)
}
}
}Ktor 沒有 Spring 的 annotation magic——沒有 @Controller、沒有 @Autowired、沒有 @Transactional。所有東西都是函式和 extension function,行為完全明確可見。
Ktor 的 plugin 系統讓功能以 plugin 形式掛載:
fun Application.configureApp() {
install(ContentNegotiation) { json() }
install(CallLogging) { level = Level.INFO }
install(Authentication) {
jwt("auth-jwt") { /* config */ }
}
configureRouting()
}缺點:相比 Spring,生態小很多(特別是 ORM 選型:Exposed 是官方 ORM 但比 JPA 成熟度低;可以用 jOOQ 或 JDBC 自己包裝)。DI 沒有內建,要自己接 Koin 或 Kodein。
適合:Kotlin 優先的團隊;不想用 Spring 的複雜度;需要 coroutine-native 的高並發場景;proto proto2661047/b2e/kotlin-ktor 有完整實作。
GC 與記憶體管理的框架層影響
JVM 的 GC(Garbage Collection)本身不是框架問題,但框架的設計會影響 GC 壓力:
- Spring Boot 的大量 reflection + proxy 在啟動時會產生很多短命物件,GC 壓力較高
- Ktor 的 Kotlin coroutine 模型讓物件生命週期更可預測,GC pause 相對穩定
- Quarkus native image 直接繞過 JVM GC——native binary 用系統記憶體,沒有 GC pause 的概念
如果你的場景對 GC pause latency 很敏感(real-time、低延遲 API),選 Quarkus native image 或 Ktor(輕量物件分配)比 Spring Boot 更合適。
JVM GC 的深入選擇(G1GC / ZGC / Shenandoah 的差異)屬於語言層的討論,放在 B05 語言篇。
選擇決策
需要 Spring 完整生態(Security / Cloud / Batch)?
→ Spring Boot
想要 Spring-like 體驗但啟動更快、記憶體更小?
→ Micronaut
Serverless / FaaS,cold start 是硬需求?
→ Quarkus(native image)
Kotlin 優先,不想用 Spring 架構?
→ Ktor
