[Java] JDK24 Virtual Thread Pinning 이슈 해결되다!?!?! (JEP 491)

🔥Log·2025년 10월 19일
0

Java

목록 보기
23/23

☕️ 들어가며


과거에 JDK21이 LTS 버전으로 선정된 후, Virtual thread라는 구미 당기는 신규 기능을 알게 되었다.
Go의 고루틴, Kotlin의 코루틴과 같은 경량 쓰레드를 Java에서도 쓸 수 있게 된 것이다.
그래서 호다다닥 해당 기능에 대해서 사용해봤었는데, 매우 치명적이게도 Pinning 이슈가 있음을 확인하였다.

Pinning 이슈

이론상, Virtual thread를 사용하면 실질적인 로직은 Virtual thread에서 실행되고, I/O작업에 대해서만 Carrier thread가 처리하는 방식이여서 Virtual thread에서 실행되는 로직과 무관하게 Carrier thread는 블락킹없이 작업을 처리할 수 있어야하는데, 로직에서 JNI가 호출되거나 synchronized 키워드를 사용하면 Carrier thread가 블로킹되버리는 이슈가 있었다.

이를 바로 Pinning 이슈라고 하며, 서비스 개발할 때 무조건 필요한 DB와 관련된 많은 라이브러리에는 synchronized 키워드가 많이 사용되어 있었다.
그래서 Virtual thread라는 혁명적인 기능이 추가되었지만 사실상 실무에서는 사용할 수 없던 상황이 됐었다.

JEP 491: Synchronize Virtual Threads without Pinning

얼마전 새로운 JDK LTS 버전이 릴리즈됐었다. JDK 25가 그 대상이다.
그래서 '오~ 또 뭐가 업데이트됐으려나'하고 신규 기능을 살펴보던 중, JDK24에 포함된 JEP 491을 알게 되었다.

처음 JEP 491 제목을 보고, 깜짝 놀라서 눈을 동그랗게 뜨고 봤던 거 같다.
공식 문서 외에도 다양한 블로그 글과 커뮤니티 글들을 확인해보니, 진짜로 Pinning 이슈가 해소된 것으로 확인했다. (JNI 호출 시 발생하는 Pinning 이슈는 아직 해소 안됨)

그래~~서, 직접 테스트 코드를 작성해서 테스트를 해보기로 했다. 🤔
바로 드가자 🔥



👨‍🍳 Virtual thread 테스트하기


🔗 깃헙

이번 글에서는 synchronized를 사용하며, DB 쿼리를 수행하는 임의의 메서드를 만들고 이 메서드를 Virtual thread를 사용하여 실행해보고, 성능을 측정해보도록 하겠다.

1) build.gradle

plugins {
	id 'java'
	id 'org.springframework.boot' version '3.5.6'
	id 'io.spring.dependency-management' version '1.1.7'
}

group = 'study'
version = '0.0.1-SNAPSHOT'

java {
	sourceCompatibility = '25'
}

configurations {
	compileOnly {
		extendsFrom annotationProcessor
	}
}

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
	implementation 'org.springframework.boot:spring-boot-starter-web'
	compileOnly 'org.projectlombok:lombok'
	runtimeOnly 'com.mysql:mysql-connector-j'
	annotationProcessor 'org.projectlombok:lombok'

	testImplementation 'org.springframework.boot:spring-boot-starter-test'
	testCompileOnly 'org.projectlombok:lombok'
	testAnnotationProcessor 'org.projectlombok:lombok'
}

tasks.named('test') {
	useJUnitPlatform()
}

Java버전은 25버전으로 세팅해주고, Spring boot, JPA,MySQL을 사용할 수 있도록 의존성을 정의해준다.
(Pinning 이슈를 테스트할 때에는 Java 버전을 21버전으로 바꿔가며 작업할 예정)

2) application.yaml

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/vt
    username: root
    password: 1234
    hikari:
      maximum-pool-size: 100
      minimum-idle: 100

설정 파일에서는 다른 값들은 중요하지 않고, DB 커넥션 풀 사이즈를 원하는 값으로 설정해준다.

3) 테스트 코드 작성

@Slf4j
@SpringBootTest
class VirtualThreadPinningTests {

	@Autowired
	private JdbcTemplate jdbcTemplate;

	// Pinning 관찰용: synchronized로 모니터를 잡은 상태에서 블로킹 호출 실행
	public void runQuery(int count) {
		Object lock = new Object();
		synchronized (lock) {
			jdbcTemplate.queryForList("select sleep(1)");
		}
		log.info("실행(pinned {}/100)", count);
	}

	@Test
	@DisplayName("Virtual Thread - pinning 이슈")
	void test() throws ExecutionException, InterruptedException {
		long startTime = System.currentTimeMillis();

		// 작업 정의
		List<Runnable> tasks = new ArrayList<>();
		for (int i = 0; i < 100; i++) {
			int count = i + 1;
			tasks.add(() -> runQuery(count));
		}

		// 작업 실행 및 종료 보장
		ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor();
		List<? extends Future<?>> futures = tasks.stream()
			.map(executorService::submit)
			.toList();

		// 작업 취합
		for (Future<?> future : futures) {
			future.get();
		}

		// 결과 출력
		long endTime = System.currentTimeMillis();
		long elapsedMs = endTime - startTime;
		double tps = 100.0 / (elapsedMs / 1000.0);

		log.info("전체 실행 시간: {} ms", elapsedMs);
		log.info("TPS: {}", String.format("%.2f", tps));
	}

}

1초 Sleep하는 쿼리를 synchronized 키워드를 붙여서 실행하도록 메서드를 만들어주고, 이를 100회 실행하는 테스트 코드를 작성하였다.



🔥 성능 테스트


1) Java 25

테스트 코드를 실행해보면, 총 2.6초 정도의 시간이 소요됐고, TPS는 37.92로 측정된 것을 확인할 수 있다.

Virtual thread가 정상적으로 동작했을 것으로 보여진다.

2) Java 21

💡 build.gradle에서 Java버전을 21로 변경하고, IDE를 사용한다면 프로젝트 실행에 사용된느 JDK의 버전도 21로 변경하고 테스트 코드를 실행한다.

테스트 코드를 실행해보면, 총 9초 정도의 시간이 소요됐고, TPS는 11.01로 측정된 것을 확인할 수 있다.

25버전과 성능 차이가 매우 많이 나고, 이를 통해서 JEP 491을 통해서 확실히 Pinning 이슈가 해결된 것으로 생각된다. 🔥🔥🔥



☕️ 마무리


Virtual thread가 처음 나왔을 때, 너무 쓰고 싶었는데 Pinning 이슈 때문에 사용할 수 없게 되어서 많이 아쉬웠었는데, 예상했던 것보다 빠르게 Pinning 이슈가 해결된 것 같아서 좋고, 개발자의 개발자님들에게 리스펙하는 마음이 드는 것 같다. 🙂

실무에서도 활용할 수 있을지 좀 더 검토하고, 회사의 팀원들에게도 공유해보도록 해야겠다! 🫡

0개의 댓글