컴파일 단계
.java 소스 코드를 javac 컴파일러가 읽어서 바이트 코드 ( Bytecode ) .class 파일로 변환
⇒ 개발자가 작성한 소스 코드를 중간 언어 ( 바이트 코드 )로 바꾸는 과정
실행 ( Interprete or JIT 컴파일 )

만들어진 .class ( 바이트 코드 )는 JVM이 읽습니다.
JVM은 바이트 코드를 한 줄씩 Interprete ( 번역 ) 하여 실행하거나, JIT ( Just-In-Time ) 컴파일러를 이용해 네이티브 기계어로 바꿔서 실행합니다.
인터프리터 방식은 바이트 코드를 읽어서 실행하므로, 코드 실행 준비가 빠르며, 프로그램 시작 시 즉시 동작 가능합니다.
하지만, 매번 바이트코드를 해석하면서 실행해야하므로, 네이티브 기계어보다 훨씬 느립니다.
JIT 컴파일 방식은 자주 실행되는 코드를 기계어로 변환하여 캐싱해두므로 실행 속도가 빠르지만 네이티브 코드로 변환하는데 시간이 오래 걸리기에 프로그램 시작 속도가 느릴 수 있습니다.
따라서, JVM은 처음엔 인터프리터로 빠르게 프로그램을 실행하며, 자주 쓰이는 부분은 JIT로 최적화를 하여 최종적으로 실행 속도와 초기 실행을 개선시키는 전략을 사용합니다.
즉, Java는 컴파일과 인터프리터 두 동작에 의해 실행되는 언어입니다.
컴파일 : 전체 소스코드를 한 번에 기계어 혹은 중간 업어로 변환하는 과정
Interprete : 코드를 한 줄씩 읽고 즉시 실행
Java는 바이트코드를 JVM이 해석하거나 JIT 컴파일을 거쳐 실행하기 때문에, C나 C++처럼 컴파일 시 바로 기계어로 변환되는 언어에 비해 성능이 다소 떨어질 수 있습니다.
이는 컴파일 언어가 런타임 환경에서 이미 준비된 기계어를 곧바로 실행할 수 있고, 컴파일 과정에서 코드 최적화까지 수행되기 때문입니다.
다만, 이러한 방식은 생성된 기계어가 빌드 시점의 CPU 아키텍처에 종속적이라는 한계가 있습니다. 따라서 다른 아키텍처에서 실행하려면 해당 환경에 맞게 새로 빌드해야 하는 단점이 존재합니다.
컴파일 언어가 인터프리터 언어보다 성능적으로 더 유리하지만 인터프리터 방식을 가지는 Java는 이러한 문제를 해결하기 위해 JVM에 JIT 컴파일러를 사용합니다.
JIT 컴파일러는 바이트 코드를 기계어로 변환하는 과정에서 이를 캐시에 저장하고 활용합니다.
이를 통해 반복되는 변환 과정을 줄여 성능을 향상시키고 런타임 환경에 맞춰 코드를 최적화하여 성능을 개선시킵니다.
하지만, 애플리케이션을 시작하는 단계에서는 캐시된 내역들이 없기 때문에 JIT 컴파일러의 성능이 좋을 수 없습니다
따라서 애플리케이션 시작 후 의도적으로 미리 로직을 실행하여 기계어가 캐시에 저장되고 최적화하는 warm up 과정이 필요
JVM warm up : 런타임 시 즉시 실행할 수 있도록 JVM이 코드를 준비하는 과정
단, 모든 클래스를 캐시에 저장하는 것은 아니며, 실제로는 실행 중 자주 사용되는 코드만 선별하여 최적화 및 캐싱합니다.
애플리케이션 실행 시 주요 기능에 대한 웜업 과정을 웜업 전용 API 엔드포인트를 통해 수행하도록 하게 되면, 이를 통해 초기 구동 시 발생하던 콜드스타트 문제를 개선하여 정상 실행 시와 동일한 성능을 보장할 수 있게 됩니다.
또한, 정해진 시간만큼 지연시키고 트래픽을 유입시키는 것이 아닌 warm up 과정이 완료 되어야 유입될 수 있도록 하면 더욱 개선될 수 있습니다.
⇒ warm up 이전의 요청에는 400응답을 통해 외부 트래픽이 유입되지 않도록 함
⇒ warm up이 완료된다면 트래픽이 유입될 수 있도록 함