Spring Framework 6.1 부터 JDK 21 (Virtual Thread) 와의 호환을 지원하기 시작한다고 합니다. 비슷하게 block 된 쓰레드에 대한 관리를 해주는 Webflux 가 Virtual Thread 덕분에 이젠 필요가 없어지는 걸까요? 먼저 Virtual Thread (가상 쓰레드) 가 기존의 쓰레드와 어떠한 점이 달라졌는지 먼저 살펴보고, 성능 차이를 확인해보도록 하겠습닌다.
기존 자바 쓰레드 동작 원리
Spring Web MVC 기준
Thread t = Thread.ofPlatform()
JVM 의 플랫폼 쓰레드를 생성합니다.- 플랫폼 쓰레드가 동작하려면 CPU 에 접근해야 합니다. 그러기 위해서는 커널 쓰레드와 매핑이 1:1 로 되어야하는데 이에 대한 중간 매개체가
JNI(Java Native Interface
입니다.t.start()
를 하면 JNI 를 통해 커널 쓰레드와 매핑이 됩니다. - 플랫폼 쓰레드와 커널 쓰레드가 1:1 매핑이 되고 나서는 CPU 를 점유해야 합니다. 이를 위해서 OS 안의 스케줄러가 존재합니다. OS 의 스케줄러가 상황에 맞게 쓰레드가 CPU 코어를 점유할 수 있도록 해줍니다.
하지만 기존의 쓰레드는 다음과 같은 문제점이 있었습니다.
- 쓰레드의 생성 (Thread.ofPlatform()) 과 스케줄링(interrupt, sleep) 에 항상 os 가 관여해야 합니다. 예를 들어 플랫폼 쓰레드 1 를 중단시키는 상황이라면, 매핑되어있는 커널 쓰레드도 같이 중단시켜야 하기 때문입니다. 이 과정에서 os 를 항상 호출
시스템 콜
이 발생하여 `컨텍스트 스위칭 오버헤드`가 발생합니다. - 또한, 블락킹 모델이기 때문에 성능이 좋지 못합니다. 어떠한 의미인지 밑의 그림에서 보도록 하겠습니다.
일반 쓰레드가 블락 된다면 어떤 일이 일어날까요?
JVM Tomcat 의 쓰레드 수는 단 2개 밖에 없는 상황입니다. 요청 1, 2 를 각각 플랫폼 쓰레드 1, 2 에서 처리하고 있었으나 플랫폼 쓰레드 2가 블락이 되었고 요청 3번이 왔습니다. 논블락킹 모델이었다면 플랫폼 쓰레드 2가 블락되지 않고 다음 요청을 처리할 수 있으나, 블락킹 모델이기 때문에 요청 3은 쓰레드 여유분이 나올 때까지 대기하게 됩니다.
Virtual Thread 동작 원리
- Virtual Thread (가상 쓰레드) 는 기본적으로 기존의 플랫폼 쓰레드보다 적은 메모리를 사용합니다. (경량 쓰레드) 그렇기 때문에 쓰레드 컨텍스트 스위칭 관점에서도 좀더 가볍게 작동합니다. 가상 쓰레드는 플랫폼 쓰레드와 N:1 매핑이 됩니다.
- Platfrom Thread: 약 2KB
- Virtual Thrad: 200~300B
- 이러한 장점 덕분에 쓰레드 생성 속도가 매우 빠릅니다.
- 가상 쓰레드는 특별히 쓰레드 풀 개념이 없습니다. 대신 JVM heap 영역에서 무한정으로 생성됩니다. (쓰레드 메모리가 매우 작기 때문에 가능한 일이겠죠?)
- 무한정으로 생성하고 GC 에 의해 항상 해제되는 1회용품입니다.
- `ForkJoinPool` : 플랫폼 쓰레드 풀 임과 동시에 스케줄러
- 가상 쓰레드 마운트/언마운트를 관리함
// 가상 쓰레드 Mount 메소드
void unpark() {
... 생략
if (s == PARKED && compareAndSetState(PARKED, RUNNABLE)) {
if (currentThread instanceof VirtualThread vthread) {
vthread.switchToCarrierThread();
try {
submitRunContinuation();
} finally {
switchToVirtualThread(vthread);
}
} else {
submitRunContinuation();
}
}
... 생략
}
}
private void submitRunContinuation() {
try {
scheduler.execute(runContinuation);
} catch (RejectedExecutionException ree) {
submitFailed(ree);
throw ree;
}
}
- 가상 쓰레드가 마운트된다면
- heap 메모리에 가상 쓰레드가 생성됨
- `this.carrierThread = carrier` : ForkJoinPool 스케줄러를 통해 플랫폼 쓰레드와 매핑됨 (캐리어 쓰레드 = 플랫폼 쓰레드)
- `submitRunContinuation()` : 플랫폼 쓰레드의 WorkQueue 를 통해 순차적 실행
- WorkQueue 는 work stealing (본인의 queue 가 비었다면 남의 queue 에서 남는 작업을 뺏어옴) 방식으로 작동
가상 쓰레드가 블락 된다면 어떤 일이 일어날까요?
제 서버로 요청이 왔습니다. 가상 쓰레드는 JVM Heap 영역에서 무한정으로 생성되기 때문에, Heap 영역에 버추얼 쓰레드 10이 생성됩니다. 생성된 버추얼 쓰레드 10은 플랫폼 쓰레드와 매핑이 되어 정상적으로 동작을 하고 있습니다.
그러나, 여기서 버추얼 쓰레드 10번이 block 된다면? (예를 들어 외부 api 호출을 한다면?) 어떠한 일이 일어날까요?
- 먼저, 블락된 버추얼 쓰레드 10번을 플랫폼 쓰레드에서 떼어내주어야 합니다. 이럴 때 쓰는 `park()` 메소드를 통해 매핑을 해제합니다. 플랫폼 쓰레드 입장에서는 블락된 버추얼 쓰레드 10번 말고 다른 일을 수행할 수 있습니다. (논블락킹)
- 만약에, 버추얼 쓰레드 10번의 블락이 해제된다면, 다시 남아있는 플랫폼 쓰레드와 매핑이 됩니다. (이전에 매핑되었던 플랫폼 쓰레드와 같을 수도 있고 다를 수도 있습니다.)
가상 쓰레드 성능
지금까지는 개념에 대한 설명이었고 그래서 도입할 만한 성능을 가졌는지 비교해보겠습니다.
먼저 Spring Web MVC 환경(왼쪽)에서 CPU Bound 일 때는 성능이 떨어지지만 IO Bound 일 때는 성능이 좋은 것으로 나타났습니다. IO Bound 의 경우, 가상 쓰레드가 논블락킹으로 작동하게 해주기 때문에 성능이 더 우수할 수 밖에 없습니다. 다만, CPU Bound 는 쓰레드가 블락될 일이 없고 그대로 플랫폼 쓰레드를 쓰기 때문에 결국엔 가상쓰레드의 생성 및 스케줄링 비용은 낭비일 수 밖에 없습니다.
Webflux 의 Reactive Programming 환경 (오른쪽) 에서 비교했을 때도, 성능이 좋은 것으로 나타났는데 이는 컨텍스트 스위칭 부분에서 일반 쓰레드와 경량 쓰레드의 차이 때문입니다. Reactive Programming 에서는 컨텍스트 스위칭 되고있는 쓰레드가 일반쓰레드이고 경량 쓰레드보다 기본적으로 무겁기 때문에 성능 차이가 난다.
주의사항 Pinned Issue
- `synchronized` 또는 `native` 메소드를 사용하면 가상 쓰레드가 블락 되었을 때 플랫폼 쓰레드에서 unmount 되지 않고 항상 붙어있습니다. 이것을 가상 쓰레드가 플랫폼 쓰레드에 고정되었다고 하여 pinned 이슈라고 합니다.
- 이러한 이유로, synchronized 를 쓰지 않거나 `ReentrantLock` 사용을 권장합니다.
참고
https://www.youtube.com/watch?v=BZMZIM-n4C0&t=2616s&ab_channel=우아한테크
https://jaeyeong951.medium.com/virtual-thread-synchronized-x-6b19aaa09af
https://d2.naver.com/helloworld/1203723
https://techblog.woowahan.com/15398/#toc-10
https://nodejs.org/en/learn/asynchronous-work/dont-block-the-event-loop
https://www.linkedin.com/pulse/spring-webflux-under-hood-diego-lucas-silva/
'📗Java' 카테고리의 다른 글
[Java] static class 와 static method 이해하기 (3) | 2024.09.21 |
---|---|
[Java] Arrays.sort() 어떤 정렬 알고리즘을 쓸까? (2) | 2024.04.30 |
[모던자바인액션] Optional (0) | 2023.10.23 |
[Java] Reflection API (5) | 2023.09.24 |
[모던자바인액션] 람다 표현식 (1) | 2023.09.14 |