CS Insights

JVM JIT 컴파일러의 극단적 런타임 최적화: 탈출 분석(Escape Analysis)과 스칼라 치환의 마법

JVM JIT 컴파일러의 극단적 런타임 최적화: 탈출 분석(Escape Analysis)과 스칼라 치환의 마법
Java로 짜인 코드가 어떻게 C++보다 빠를 수 있냐는 후배의 질문에, 저는 코드를 보여주는 대신 JVM의 C2 컴파일러 플래그를 켜서 동적 어셈블리가 생성되는 과정을 모니터링 시켜주었습니다. C나 C++ 같은 AOT(Ahead-of-Time) 언어들은 실행 전 컴파일 타임에 모든 최적화가 결정됩니다. 하지만 Java나 C#의 JIT(Just-In-Time) 컴파일러는 프로그램이 실행되는 런타임 내내 애플리케이션의 핫스팟(가장 자주 실행되는 병목 구간)을 실시간으로 감시하고 프로파일링합니다. 만약 어떤 메서드가 수만 번 반복 호출됨을 감지하면, C2(서버 컴파일러)는 즉각 개입하여 이 바이트코드를 그제서야 가장 공격적인 네이티브 머신 코드로 번역하며 극단적인 튜닝을 가합니다. 이 JIT 최적화의 백미가 바로 '탈출 분석(Escape Analysis)'입니다. 개발자가 분명히 new 키워드로 객체를 생성하여 강제로 힙(Heap) 메모리에 할당하도록 코딩을 했음에도 불구하고, JIT 컴파일러는 이 객체의 생명주기를 분석합니다. 만약 특정 객체가 그 메서드 블록 내부에서만 쓰이고 외부로 참조가 반환(Escape)되지 않는다는 것이 증명되면, 컴파일러는 개발자의 코드를 무시하고 이 객체의 할당 위치를 힙이 아닌 빠른 스택(Stack) 메모리 영역으로 몰래 강등시켜 꽂아버립니다. 더 나아가 스칼라 치환(Scalar Replacement) 기법이 발동되면, 아예 객체 자체를 쪼개어 그 안의 멤버 변수들을 개별적인 CPU 레지스터에 흩뿌려버립니다. 메모리 할당 자체가 원천적으로 사라지는 것입니다. 가비지 컬렉터(GC)가 치워야 할 쓰레기조차 만들어지지 않게 되어 시스템 지연은 제로로 수렴합니다. 정적 언어가 태생적으로 가질 수 없는, 런타임 생태계의 맥락을 이해하고 실시간으로 변이를 일으키는 JVM의 생체 공학적 설계 철학에 감탄할 수밖에 없던 디버깅 세션이었습니다.

Related Posts