자바의 특징에 대해 검색하면 항상 나오는 내용이 있다.
"Write once, run anywhere"
직역하자면 "한번 작성하면 어디서나 실행 가능하다" 인데, 이건 곧 운영 체제에 종속되지 않는다는 것이다.
실제로, C언어의 경우 어떤 OS에서 컴파일 했는지에 따라 실행할 수 있는 환경 또한 정해진다. 자바는 과연 어떻게 한번만 작성하면 어디에서나 실행할 수 있는 걸까?
우리가 자바 코드를 작성하면 해당 파일은 .java 확장자를 갖는다. 이 자바 파일은 자바 컴파일러(javac)에 의해 바이트 코드 (.class) 파일을 생성한다.
컴파일 과정을 거친 바이트 코드는 JVM에 의해 실행되므로, 운영체제에 종속되지 않을 수 있다.
그렇다면 JVM은 뭘까?
JVM은 컴파일 된 바이트 코드를 OS가 이해할 수 있는 기계어로 바꾸어 실행하는 역할을 한다. 바꿔 말하면 JVM은 바이트 코드를 실행하는 주체이며, 바이트 코드는 JVM을 거쳐 실행되어야 한다.
JVM은 크게 4가지 요소로 구성된다.
이 중 클래스 로더와 실행 엔진의 동작을 확인해보자.
클래스 로더는 필요한 클래스를 검색하여 JVM에 적재하는 동작을 한다. 이 클래스 로더는 3개가 존재하며, 몇가지 원칙을 따르며 클래스를 탐색한다.
클래스 로더의 원칙은 다음과 같다.
위 세가지 원칙을 지키며, 클래스 로더는 다음과 같이 동작한다.
실행 엔진은 클래스 로더에 의해 로드된 바이트 코드를 실행한다.
컴퓨터는 바이트 코드를 이해할 수 없다. 때문에, 실행엔진은 이를 운영체제에 맞게끔 바이너리 파일로 변환을 하여 실행한다. 이 과정을 통해 Java의 가장 큰 특징인 "Write Once, Run Anywhere" 를 구현한다.
실행 엔진은 기본적으로 인터프리터 방식으로 동작한다. 한번 컴파일 된 바이트 코드를 라인별로 읽어 바이너리로 변환 후 실행하는데, 이러한 방식을 통해 OS로부터 자유로워 질 수 있었다.
다만, 인터프리터 방식은 속도가 느리다. 느리다는건 곧 어플리케이션의 성능 하락으로 이어진다. Java는 이러한 단점을 보완하기 위해 JIT Compiler를 도입했다.
이 JIT 컴파일러는 자주 사용되는 코드를 런타임에 바이너리로 컴파일함으로써 성능을 향상시킨다.
실행 중 컴파일 임계치(Compile Threshold)를 넘은 코드는 JIT 컴파일러에 의해 컴파일되며, 임계치는 메소드가 호출된 횟수와 메소드가 루프를 빠져나오기 까지의 횟수를 기준으로 정한다.
또한 JIT 컴파일러는 Code Caching 혹은 다양한 최적화 전략을 사용함으로써 높은 성능을 이끌어낸다.
정프로님 블로그 - JVM 구조와 자바 런타임 메모리 구조
infoworld - All about Java class loaders
Geeksforgeeks - ClassLoader in Java
Turing - # Guide to the Basics of ClassLoader
강준현님 블로그 - JVM 실행 엔진
IBM - reference-jit-compiler