안녕하세요. 오늘은 자바의 가상 스레드(Virtual Thread)에 대해서 알아보겠습니다. 이 가상 스레드는 JDK21에서 정식으로 추가된 기능입니다.
기존 Thread의 한계
기존에 사용되던 자바의 스레드는 플랫폼 스레드(Platform Thread)라고 불립니다. 이 플랫폼 스레드는 OS의 커널 스레드(Kernel Thread)와 1대 1로 매핑되는 형태입니다. 그렇다면 플랫폼 스레드는 커널 스레드가 가진 단점을 그대로 가지고 있다는 것입니다.
커널 스레드의 단점은 아래와 같습니다.
- OS가 스케줄링을 할 때 발생하는 컨텍스트 스위칭(Context Switching)의 비용이 많다.
- 일반적으로 하나의 스레드마다 메모리 1MB를 스택을 위해 할당된다.
저희가 흔히 알고 있는 자바의 스프링 프레임워크(Spring Framework)는 thread-per-request 모델입니다. 번역하면 "하나의 요청에 하나의 스레드가 배정된다"라는 뜻이 됩니다.
저희가 스프링을 이용해 서버를 만들고 서비스를 운영하고 있고, 서비스가 너무 잘되어서 동시에 API 호출이 4000개씩 들어온다고 가정해 봅시다. thread-per-request라면 스레드가 4000개가 만들어지겠죠. 이러면 메모리를 4000MB(~4GB) 정도 차지하게 될 겁니다. 디바이스의 메모리가 4GB라면 OOM(Out of Memory)이 발생할 수도 있습니다. 또한 CPU의 코어 개수는 한정적이기 때문에 컨텍스트 스위칭에도 많은 비용이 차지할 겁니다. 그렇다면 제대로 된 서비스가 이루어지지 않겠죠.
그래서 많은 자바 개발자는 다른 언어의 비동기적인 요소를 원해왔습니다. 실제로 그런 라이브러리들도 많이 나왔구요. 그래서 가상 스레드라는 것이 나오게 되었습니다.
Virtual Thread의 동작 방식
앞서 가상 스레드가 등장하게 된 배경에 대해 설명드렸습니다. 이제는 본격적으로 가상 스레드가 어떻게 작동하는지 알아봅시다.
이전의 플랫폼 스레드는 OS의 커널 스레드와 1대 1로 매핑된다는 것을 언급했었습니다. 하지만 가상 스레드는 조금 다르게 동작합니다.
플랫폼 스레드는 여전히 사용됩니다. 커널 스레드는 프로그램 실행에 있어서 필수이기 때문입니다. 하지만 플랫폼 스레드는 기존 방식만큼 많이 사용되지 않습니다. 가상 스레드가 많이 생겨도, 플랫폼 스레드의 수는 가상 스레드의 수보다 적습니다. ForkJoinPool이라는 객체가 스케줄링을 담당하기 때문입니다.
가상 스레드가 등록되면 ForkJoinPool은 해당 가상 스레드를 어떤 플랫폼 스레드 큐에 배치해서 실행시킬지 결정합니다. 그 상태에서 가상 스레드가 WAITING, BLOCK 되면 해당 가상 스레드를 후순위로 미뤄놓고 다음 가상 스레드를 플랫폼 스레드에 배치해서 실행시킵니다. TERMINATED 되면, 바로 다음 가상 스레드를 실행합니다. 제 생각에는 JS의 이벤트루프와 유사한 방식이라고 생각이 드네요.
이러한 방식을 사용함으로써 컨텍스트 스위칭 비용을 크게 감소시켰습니다. OS가 스케줄링을 하지 않고 직접 가상 스레드의 스케줄링을 하기 때문입니다. 기존 스레드 방식의 첫 번째 문제를 해결했음을 알 수 있습니다.
또한 메모리도 적게 차지합니다. 정확히 하나의 가상 스레드가 얼마의 메모리를 차지하는지는 알 수 없지만, 테스트해 본 결과 매우 적은 메모리를 차지하는 것을 알 수 있었습니다. 해당 테스트 내용은 향후에 작성하도록 하겠습니다. 이렇게 기존 스레드 방식의 두 번째 문제도 해결했음을 알 수 있습니다.
하지만 속도는 극적으로 빨라지지는 않는다.
가상 스레드를 사용하면서 염두하셔야 할 것은 실행 속도가 빨라지지는 않는다는 것입니다. 어찌되었든 실행은 기존과 똑같이 플랫폼 스레드에서 실행됩니다. 컨텍스트 스위칭에서 발생하는 시간은 조금 줄어들 수 있겠지만, CPU bound 코드 실행은 기존과 똑같은 실행 시간을 가질 것입니다. 가상 스레드는 속도 상승이 아닌, 처리량 증가에 목적이 있습니다.
JEP444에서 밝히는 가상 스레드의 목적
JEP 444: Virtual Threads
JEP 444: Virtual Threads AuthorRon Pressler & Alan BatemanOwnerAlan BatemanTypeFeatureScopeSEStatusClosed / DeliveredRelease21Componentcore-libsDiscussionloom dash dev at openjdk dot orgRelates toJEP 436: Virtual Threads (Second Preview)Reviewed byAlex
openjdk.org
- 서버 어플리케이션 작성 시 하드웨어 최적화가 가능한 thread-per-request 스타일을 가능하게 하자.
(Enable server applications written in the simple thread-per-request style to scale with near-optimal hardware utilization.)- Spring Boot에 적용할 수 있음. 같은 메모리로, 더 많은 동시성이 가능해짐
- 기존 java.lang.Thread API를 사용하는 코드에서도 최소한의 변경으로 virtual thread를 사용 가능하게 하자.
(Enable existing code that uses the java.lang.Thread API to adopt virtual threads with minimal change.)- 기존 Thread API를 그대로 사용가능함. Thread 객체를 실행하는 코드만 조금 변경됨
- 기존 플랫폼 스레드도 사용 가능함
- 현재 존재하는 JDK 툴로도 가상 스레드의 트러블 슈팅, 디버깅, 프로파일링을 쉽게 만들자.
(Enable easy troubleshooting, debugging, and profiling of virtual threads with existing JDK tools.)
마무리
오늘은 가상 스레드에 대해서 알아봤습니다.
정리하자면, 가상 스레드는 컨텍스트 스위칭 오버헤드 제거, 메모리 효율화를 통해 기존 스레드보다 나은 성능을 가집니다. 하지만 실행 속도는 기존과 비슷합니다.
저는 향후 가상 스레드와 플랫폼 스레드의 성능 비교 결과를 가지고 포스팅해 볼 예정입니다. 감사합니다.
'JVM > Java' 카테고리의 다른 글
Java의 동기화(synchronized, wait(), notify()) (0) | 2025.02.03 |
---|---|
Java Virtual Thread 사용하는 방법 (0) | 2025.02.03 |
Java에서 MessageDigest를 이용해서 해시코드 생성하기 (SHA-256, SHA-512, MD5) (0) | 2025.02.02 |