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 Boot3–8 秒~200–500 MBJava / KotlinJVM 企業標準,生態最完整
Micronaut< 1 秒~50–100 MBJava / Kotlin / GroovyAhead-of-time DI,解 Spring 啟動問題
Quarkus< 1 秒(JVM)/ < 0.1 秒(native)~30–80 MB(native)Java / KotlinGraalVM native image,serverless 最優
Ktor< 0.5 秒~30–60 MBKotlinKotlin 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

延伸閱讀