[Java] Virtual Thread
우아한 테크 세미나 [Java의 미래, Virtual Thread]를 보고 정리한 내용입니다.
- 2018년 Project Loom으로 시작된 경량 스레드 모델
- 2023년 JDK 21에 정식 feature로 포함
장점
1. 스레드 생성 및 스케줄링 비용이 기존 스레드 보다 저렴
- 기존 스레드
- 기존 자바 스레드는 생성 비용이 크다
- 스레드 풀: 자바의 스레드 생성과 스케줄링 비용이 크기 때문에 미리 여러 개의 스레드를 만들어 놓고 요청이 올 때마다 스레드를 사용
- 사용 메모리 크기가 크다 - 최대 2MB까지
- OS에 의해 스케줄링
- 스레드 생성/소멸시 항상 OS와 통신해야 하기 때문에 System Call 발생 ⇒ 시스템콜 오버헤드 발생
- 기존 자바 스레드는 생성 비용이 크다
- virtual thread
- 생성 비용이 작다
- 스레드 풀 개념이 존재하지 않고, 항상 새로 생성
- 사용 메모리 크기가 작다 - 수십 kb까지만
- OS가 아닌 JVM 내 스케줄링 → 시스템콜에 의한 오버헤드가 발생하지 않음
- 생성 비용이 작다
| Thread | VirtualThread | |
| 메모리 사이즈 | ~2MB | ~50KB |
| 생성 시간 | ~1ms | ~10us |
| 텍스트 스위칭 시간 | ~100us | ~10us |
2. 스레드 스케줄링을 통해 Nonblocking I/O 지원
- I/O Blocking
- 복잡한 비즈니스 로직에 따른 증가하는 I/O blocking time
- Thread per request 모델에서는 특히 병목이 되는 blocking time
- Non blocking I/O
- Spring WebFlux는 netty의 event loop를 이용해 blocking time을 획기적으로 줄임
- virtual thread인 경우 다음을 활용하여 Non blocking I/O를 가능하게 함
- JVM 스레드 스케줄링
- Continuation 활용
- JVM 스레드 스케줄링
- Continuation 활용
3. 기존 스레드를 상속하여 코드 호환
final class VirtualThread extends BaseVirtualThread {
abstract sealed class BaseVirtualThread extends Thread
permits VirtualThread, ThreadBuilders.BoundVirtualThread {
=> VirtualThread == Thread
public ExecutorService executorService() {
return Executors.newVirtualThreadPerTaskExecutor();
}
Virtual Thread 동작 원리
일반 스레드 특징
- 플랫폼 스레드
- OS에 의해 스케줄링
- 커널 스레드와 1:1 매핑
- 작업 단위 Runnable
- 유저 영역에서 플랫폼스레드 생성 → JNI 커널영역에 요청 → OS에 커널 스레드 생성 및 스케줄링
Virtual Thread
- 가상 스레드
- JVM에 의해 스케줄링
- 캐리어 스레드와 1:N 매핑
- 작업 단위 Continuation(실행 가능한 작업 흐름으로, 중단 시점부터 다시 시작 가능)
- 사용 이유
- Thead는 작업 중단을 위해 커널 스레드를 중단
- virtual thread는 작업 중단을 위해 continuation yield
⇒ 커널 스레드 중단이 없으므로 시스템 콜 x → 컨텍스트 스위칭 비용이 낮음
- 사용 이유
- 동작 과정
- 유저 영역에 Virtual Thread 생성
- ForkJoinPool을 스케줄러로 사용
- 프로세서 수의 Carrier Thread를 worker thread로 사용
- ForkJoinPool은 Work Stealing 방식으로 작업을 수행
- WorkQueue에 task를 담아서 순차적으로 수행
- 본인의 WorkQueue가 비워있으면 남의 WorkQueue에서 훔쳐옴
- JVM 스케줄링 이유
- Thread는 생성 및 스케줄링 시 커널 영역 접근
- Virtual Thread는 커널 영역 접근 없이 단순 java 객체 생성
Virtual Thread 주의 사항
Blocking carrier thread(Pin)
- 캐리어 스레드를 block 하면 virtual thread 활용 불가
- synchronized
- parallelStream
- VM Option 으로 감지 가능
⇒ ReentrantLock으로 바꾸면 가능
- Spring Boot 3.2, MongoDB는 지원
- MySQL에서는 지원하지 않아 pin이슈가 많이 발생
- 병목 가능성이 존재하므로 사용 라이브러리 release 점검
- 변경 가능하다면 java.util의 ReentrantLock을 사용하도록 변경
No Pooling
- 생성 비용이 저렴
- 사용할 때마다 생성
- 사용 완료 후 GC
CPU bound task
- 결국 Carrier Thread 위에서 동작하므로 성능 낭비
- nonblocking의 장점을 활용하지 못함
경량 스레드
- 수백 만개의 스레드 생성 컨셉이므로 Thread local을 최대한 가볍게 유지해야 함 => 쉽게 생성 및 소멸
- JDK 21 preview ScopedValue → 스레드 개념을 대체하는 개념
배압
- Virtual Thread는 배압 조절 기능이 없음
- 유한 리소스의 경우 배압을 조절하도록 설정이 필요 (DB 커넥션, 파일)
- 충분한 성능 테스트가 필요
- Virtual Thread는 가볍고, 빠르고, nonblocking인 경량 스레드
- Virtual Thread는 JVM 스케줄링 + Continuation
- Thread per request 사용 중이고, IO blocking time이 주된 병목인 경우 고려
- Reactive와 Kotline coroutine의 러닝 커브로 부담되는 경우, 쉽게 적용 가능
'BackEnd > Java' 카테고리의 다른 글
| [Java] Java 21 특징 (3) | 2024.07.15 |
|---|---|
| [Java] Java 17 특징 (0) | 2024.06.24 |
| [Java] Java 11 특징 (0) | 2024.06.21 |
| [Java] Java 8 특징 (0) | 2024.06.21 |