Java / JVMintermediate검토 2026.08

Virtual Thread

Blocking I/O 대기 비용을 줄이면서 Thread-per-request 코드를 유지하는 JVM 경량 Thread 모델이다.

#virtual-thread#JDK21#blocking-io#carrier-thread

Overview

Virtual Thread는 JDK 21에서 정식 도입된 경량 Thread다. Platform Thread처럼 작업마다 OS Thread를 계속 점유하지 않고, JVM이 여러 Virtual Thread를 소수의 Carrier Thread 위에서 실행한다.

한 문장 설명: I/O를 기다리는 작업을 Carrier Thread에서 내려놓아, 기존의 동기식 코드를 유지하면서 더 많은 동시 요청을 다루게 한다.

왜 필요한가

전통적인 Thread-per-request 서버는 요청 수만큼 Platform Thread가 필요하다. Database나 외부 API를 기다리는 동안에도 OS Thread가 묶이므로 Thread 수가 처리량의 한계가 된다. Reactive 방식은 대기를 효율화하지만 코드와 Debugging 모델이 달라지는 비용이 있다. Virtual Thread는 동기식 제어 흐름을 유지하면서 이 대기 비용을 줄이는 선택지다.

내부 동작

Virtual Thread가 Blocking I/O를 만나면 JVM은 실행을 중단하고 Carrier Thread를 다른 Virtual Thread에 할당한다. I/O 준비가 끝나면 해당 작업은 다시 실행 가능한 상태가 된다.

flowchart LR; A[요청마다 Virtual Thread] --> B[Carrier Thread에서 실행]; B --> C{Blocking I/O}; C -->|대기| D[Unmount]; D --> E[다른 Virtual Thread 실행]; C -->|완료| F[응답]

Virtual Thread는 값싼 실행 단위지만 Database Connection, 외부 API 허용량, Memory 같은 하위 Resource까지 무한하게 만들지는 않는다.

Example

try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    Future<String> result = executor.submit(() -> blockingHttpCall());
    System.out.println(result.get());
}

작업마다 새 Virtual Thread를 만드는 방식이 기본이다. Platform Thread의 비용을 아끼기 위해 사용하던 고정 Thread Pool을 그대로 모방할 필요는 없다.

실무에서 발생하는 문제

  • CPU 집약 작업은 대기 시간이 적으므로 Virtual Thread만으로 처리량이 증가하지 않는다.
  • 요청 수가 급격히 늘면 DB Connection Pool과 외부 API가 먼저 포화될 수 있다.
  • synchronized 구간이나 Native 호출에서 Carrier Thread가 고정되는 Pinning을 관찰해야 한다.
  • Virtual Thread마다 큰 ThreadLocal 값을 두면 전체 Memory 사용량이 커질 수 있다.

Trade-off

동기식 코드의 단순성과 높은 I/O 동시성을 얻는 대신, 기존 Thread Pool이 담당하던 동시 요청 제한을 Semaphore, Rate Limiter, Connection Pool 같은 별도 경계로 설계해야 한다.

흔한 오해

  • Virtual Thread는 Platform Thread를 완전히 없애지 않는다. Carrier는 Platform Thread다.
  • CPU 작업을 더 빠르게 계산하는 기능이 아니다.
  • Virtual Thread 수를 늘렸다고 시스템 전체 처리량이 자동 증가하지 않는다.
  • WebFlux를 무조건 대체하지 않는다. Streaming과 Reactive 생태계가 필요한 경우 선택 기준이 다르다.

Interview Questions

  1. Virtual Thread와 Platform Thread의 Mapping 차이는 무엇인가?
  2. Virtual Thread를 일반적인 고정 Thread Pool에 넣지 않는 이유는 무엇인가?
  3. Pinning은 무엇이며 어떤 운영 지표와 함께 확인해야 하는가?
  4. Virtual Thread 도입 후 DB Connection Pool이 병목이 되는 이유는 무엇인가?

Executor와 Thread Pool, Latency와 Throughput, 부하 테스트와 함께 보면 실행 단위와 실제 Resource 한계를 구분할 수 있다.

SOURCE REFERENCES

이 문서의 근거

본문은 Dev Atlas 안에서 완결되며, 검증이 필요할 때만 원문을 확인할 수 있습니다.

원문 출처 보기 2