ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Java 21 버추얼 스레드 원리와 Spring Boot 설정 방법
    Server/Spring Boot 2026. 1. 5. 16:39
    728x90
    반응형

    1. 버추얼 스레드란?

    Java 21에서 정식 도입된 경량 스레드입니다. 기존 플랫폼 스레드와 달리 JVM이 직접 관리하며, 메모리 사용량이 적고 수십만 개까지 생성할 수 있습니다.

    플랫폼 스레드 vs 버추얼 스레드

    구분 플랫폼 스레드 버추얼 스레드
    메모리 1MB+ 수 KB
    동시 개수 수백 개 수십만 개
    관리 주체 OS JVM
    스위칭 비용 높음 낮음

    2. 버추얼 스레드 동작 원리

    구조

    버추얼 스레드는 단독으로 실행되지 않습니다. 캐리어 스레드(플랫폼 스레드) 위에서 실행됩니다.

    버추얼 스레드 (수만 개 생성 가능)
    ┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐┌──┐...
    └──┘└──┘└──┘└──┘└──┘└──┘└──┘└──┘
                  ↓ JVM 스케줄링
    캐리어 스레드 (기본값: CPU 코어 수)
    ┌─────┐┌─────┐┌─────┐┌─────┐
    │  C1 ││  C2 ││  C3 ││  C4 │
    └─────┘└─────┘└─────┘└─────┘
                  ↓
             OS 스레드
                  ↓
             CPU 코어

    마운트와 언마운트

    버추얼 스레드 전환을 마운트/언마운트라고 부릅니다.

    [마운트]   : 버추얼 스레드가 캐리어 스레드에 올라타서 실행
    [언마운트] : 버추얼 스레드가 캐리어 스레드에서 분리

    동작 흐름:

    버추얼 스레드 A: 실행 중 → DB 대기 → [언마운트]
                                             ↓
    캐리어 스레드:   A 실행  →  비어있음  →  B 실행
                                             ↑
    버추얼 스레드 B:           대기 중   → [마운트]

    I/O 작업(DB 조회, API 호출 등)으로 대기 상태가 되면 자동으로 언마운트되고, 다른 버추얼 스레드가 마운트됩니다.

    OS 컨텍스트 스위칭 vs 마운트/언마운트

    구분 OS 컨텍스트 스위칭 마운트/언마운트
    발생 위치 커널 JVM (유저 모드)
    대상 플랫폼 스레드 간 버추얼 스레드 간
    비용 무거움 (수천 CPU 사이클) 가벼움 (수백 CPU 사이클)
    저장 범위 전체 CPU 상태 스택만

    핵심 정리

    버추얼 스레드는 수만 개 생성해도, 실제 OS 컨텍스트 스위칭은 코어 수 정도의 캐리어 스레드 사이에서만 일어나고, 나머지는 JVM 레벨의 가벼운 마운트/언마운트로 처리되어 효율적이다.


    3. Spring Boot 버추얼 스레드 설정

    요구사항

    • Java 21 이상
    • Spring Boot 3.2 이상

    주의: 두 가지 설정이 별개입니다

    ┌─────────────────────────────────────────────────┐
    │                  HTTP 요청                       │
    └─────────────────────┬───────────────────────────┘
                          ↓
    ┌─────────────────────────────────────────────────┐
    │   톰캣 스레드                                    │
    │   → spring.threads.virtual.enabled 설정         │
    └─────────────────────┬───────────────────────────┘
                          ↓
    ┌─────────────────────────────────────────────────┐
    │          Controller → Service → Repository       │
    │                      ↓                           │
    │              @Async 메서드 호출                   │
    │                      ↓                           │
    │   Executor Bean                                  │
    │   → 별도 설정 필요                               │
    └─────────────────────────────────────────────────┘

    설정 1: 톰캣 요청 처리용

    HTTP 요청을 처리하는 톰캣 스레드에 적용됩니다.

    # application.yml
    spring:
      threads:
        virtual:
          enabled: true

    설정 2: @Async 비동기 작업용

    @Async 어노테이션을 사용하는 비동기 작업에 적용됩니다.

    @Configuration
    @EnableAsync
    public class AsyncConfig {
    
        @Bean
        public Executor taskExecutor() {
            return Executors.newVirtualThreadPerTaskExecutor();
        }
    }

    플랫폼 스레드 풀 vs 버추얼 스레드 Executor

    플랫폼 스레드 (기존 방식)

    @Bean
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(10);   // 기본 스레드 수
        executor.setMaxPoolSize(50);    // 최대 스레드 수
        executor.setQueueCapacity(100); // 대기열 크기
        return executor;
    }
    • 스레드가 무거워서 풀로 관리
    • 개수 제한 필수

    버추얼 스레드

    @Bean
    public Executor taskExecutor() {
        return Executors.newVirtualThreadPerTaskExecutor();
    }
    • 작업마다 새로 생성
    • 풀링하지 않음
    • 개수 제한 없음

    캐리어 스레드 설정 (선택사항)

    보통 기본값으로 충분하지만, 필요시 JVM 옵션으로 조정할 수 있습니다.

    # 캐리어 스레드 수 (기본값: CPU 코어 수)
    -Djdk.virtualThreadScheduler.parallelism=8
    
    # 최대 캐리어 스레드 수
    -Djdk.virtualThreadScheduler.maxPoolSize=256

    4. 전체 설정 예시

    build.gradle

    plugins {
        id 'java'
        id 'org.springframework.boot' version '3.2.0'
    }
    
    java {
        toolchain {
            languageVersion = JavaLanguageVersion.of(21)
        }
    }

    application.yml

    spring:
      threads:
        virtual:
          enabled: true

    AsyncConfig.java

    @Configuration
    @EnableAsync
    public class AsyncConfig {
    
        @Bean
        public Executor taskExecutor() {
            return Executors.newVirtualThreadPerTaskExecutor();
        }
    }

    이렇게 설정하면 톰캣 요청 처리와 @Async 비동기 작업 모두 버추얼 스레드로 동작합니다.


    5. 정리

    설정 적용 범위 방법
    spring.threads.virtual.enabled 톰캣 HTTP 요청 application.yml
    Executor Bean @Async 비동기 작업 Java Config

    기존 코드 변경 없이 설정만으로 버추얼 스레드를 적용할 수 있습니다. WebFlux처럼 코드 스타일을 바꿀 필요 없이 성능 향상을 기대할 수 있다는 점이 가장 큰 장점입니다.

    728x90
    반응형

    댓글

Designed by Tistory.