JVM에서 synchronized는 실제로 어떻게 동작하는가?

최승원·2026년 2월 27일

백엔드에서 동시성 문제는 결국 Race Condition, Deadlock, Throughput 저하, GC 압박 증가로 이어집니다.
오늘은 반드시 알아야 할 주제인 synchronized에 대해 알아보겠습니다.

오늘 배운 것

객체 헤더 구조

모든 객체는 메모리에서 다음과 같은 구조를 가집니다.

| Mark Word | Klass Pointer | (Padding) | Instance Data |

여기서 핵심은 Mark Word입니다.
Mark Word에 들어가는 정보는 HashCode, GC age, Lock 상태, Thread ID입니다.

synchronized가 걸리면 무슨 일이 일어나는가?

monitor에 진입하며 아래의 코드는

synchronized (lock) {
    // critical section
}

아래의 바이트 코드로 변환됩니다.

monitorenter
...
monitorexit

의미는 다음과 같습니다.
락을 건다 = monitorenter 명령어 실행
락을 푼다 = monitorexit 명령어 실행

여기서 Monitor는 JVM 내부의 동기화 구조입니다.

monitorenter 실행 시,
1. 객체의 Mark Word 확인
2. 현재 소유 스레드 확인
3. CAS로 소유권 획득 시도
4. 실패 시 대기 큐 진입

Lock 단계

Biased Lock은 JDK 15부터 기본 비활성화, JDK 21에서는 완전히 제거되었습니다.

따라서 요즘 환경인 Java 17, 21 환경에서는 다음 두 단계만 고려하면 됩니다.

  1. Lightweight Lock (Thin Lock)
  • CAS 기반
  • 경쟁이 없으면 매우 빠름
  • OS 호출 없음
  1. Heavyweight Lock (Monitor Lock)
  • OS Mutex 사용
  • 스레드 블로킹
  • Context Switching 발생
  • Throughput 급감

Lock Escalation

경쟁이 발생하면, Lightweight 단계가 Heavyweight 단계로 승격됩니다. 한 번 Heavyweight로 승격되면, 다시 Lightweight로 돌아가지 않습니다.

GC와의 관계

Heavyweight Lock 상태에서는 스레드 Block 증가, 객체 생존 시간 증가, Old Gen 승격 가능성 증가, GC Pause 시간 증가 현상이 발생합니다. 따라서 락 설계는 GC 설계와도 연결됩니다.

마무리하며

synchronized는 단순한 키워드가 아니라, 객체 헤더의 Mark Word를 변경하는 JVM 레벨의 동작이며, 경쟁이 발생하면 OS Mutex 기반의 Heavyweight Lock으로 승격되어 컨텍스트 스위칭 비용을 유발하고, Virtual Thread 환경에서는 carrier thread pinning 문제를 초래할 수 있으며, 잘못 설계될 경우 전체 TPS 저하와 GC 부담 증가로까지 이어질 수 있습니다.

profile
안녕하세요. 최승원입니다.

0개의 댓글