오늘의 포스팅 주제는 JVM이다. 자바를 배울 당시 깊게 공부하지 못해서 헷갈리는 부분이 많아 정리해 보려한다.
JVM(Java Virtual Machine)은 이름 그대로 자바 가상 머신을 의미하며 자바 프로그램 실행환경을 만들어 주는 소프트웨어이다. 자바 코드를 컴파일하여 .class 바이트 코드로 만들면 이 코드가 자바 가상 머신 환경에서 실행된다. JVM은 자바 실행 환경 JRE(Java Runtime Environment)에 포함되어 있다. 현재 사용하는 컴퓨터의 운영체제에 맞는 자바 실행환경(JRE)이 설치되어 있다면 자바 가상 머신이 설치되어 있다는 뜻이다.
Java는 어떠한 플랫폼에 영향을 받지 않는다.
JVM을 사용함으로써 얻는 가장 큰 이점은 하나의 바이트 코드(.class)로 모든 플랫폼에서 동작하도록 할 수 있는 것이다.

예를 들어 C언어로 작성된 Test.c가 있다고 해보자. 이 Test.c를 윈도우 컴파일러를 사용해서 컴파일하면 Test.exe가 만들어진다. 윈도우 컴파일러로 컴파일되었기에 Test.exe는 윈도우에서만 실행되는 실행 파일이다. 리눅스 운영체제에서는 실행할 수 없다. 즉 C / C++에서는 컴파일 플랫폼과 타겟 플랫폼이 다를 경우에는 프로그램이 동작하지 않는다. 만약 이 Test.exe 파일을 리눅스 운영체제에서 실행하려면 리눅스 환경을 타겟으로 크로스 컴파일을 해서 리눅스 운영체제에 맞는 실행 파일을 새로 만들어야 한다.

Java의 경우에는 Java언어로 작성된 Test.java는 컴파일하면 Test.class 파일이 생성된다. 그리고 이렇게 생성된 바이트 코드는 각자의 플랫폼에 설치되어 있는 자바 가상 머신(JVM)이 운영체제에 맞는 실행 파일로 바꿔준다. 즉 Java에서는 C와는 달리 JVM을 사용하기 때문에 각자의 플랫폼에 맞게끔 컴파일을 따로따로 해줘야 할 필요가 없다. 하나의 바이트 코드로 JVM이 설치되어 있는 모든 플랫폼에서 동작이 가능하다.
Java는 플랫폼에 종속적이지 않지만 JVM은 플랫폼에 종속적이다.
이렇게 Java는 컴파일된 바이트코드로 어떤 JVM에서도 동작시킬 수 있기 때문에 플랫폼에 의존적이지 않다. 하지만 반대로 자바 가상 머신(JVM)은 플랫폼에 의존적이다. 즉 리눅스의 JVM과 윈도우의 JVM은 서로 다르다. 자바로 작성된 모든 프로그램은 자바 가상 머신에서만 실행될 수 있으므로, 자바 프로그램을 실행하기 위해서는 반드시 자바 가상 머신이 설치되어 있어야 한다. 따라서 오라클은 대부분의 주요 운영체제뿐만 아니라 웹 브라우저, 스마트 폰, 가전기기 등에서도 자바 가상 머신을 손쉽게 설치할 수 있도록 지원하고 있다.

자바로 .java 코드를 작성하고 파워쉘이나 터미널에 있는 자바 컴파일러인 javac에 컴파일 명령을 내리면 .class 파일이 만들어진다. 이후 이 바이트 코드는 클래스 로더를 통해 JVM Runtime Data Area로 로딩되고 로딩된 .class 바이트 코드를 실행할 컴퓨터에 깔린 JVM에 가져다주면 그 컴퓨터가 이 프로그램을 실행할 때 이 JVM이 그때그때 기계어로 해석한다.
JVM은 바이트코드를 명령어 단위로 읽어서 해석하는데, Interpreter 방식과 JIT 컴파일 방식 두 가지 방식을 혼합하여 사용한다. 먼저 Interpreter 방식은 바이트코드를 한 줄씩 해석, 실행하는 방식이다. 초기 방식으로, 속도가 느리다는 단점이 있습니다.
이렇게 느린 속도를 보완하기 위해 나온 것이 JIT(Just In Time) 컴파일 방식이다. 바이트코드를 JIT 컴파일러를 이용해 프로그램을 실제 실행하는 시점(바이트코드를 실행하는 시점)에 각 OS에 맞는 Native Code로 변환하여 실행 속도를 개선하였다. 하지만, 바이트코드를 Native Code로 변환하는 데에도 비용이 소요되므로, JVM은 모든 코드를 JIT 컴파일러 방식으로 실행하지 않고, 인터프리터 방식을 사용하다가 일정 기준이 넘어가면 JIT 컴파일 방식으로 명령어를 실행한다.

기존의 자바는 인터프리터 방식으로 명령어를 하나씩 실행하게끔 이루어져 있어 실행 속도가 느렸다. 하지만 하드웨어가 발전하면서 자바 컴파일러도 JIT 컴파일러 방식으로 개선되어 속도적인 측면에서 상당한 개선을 이루었다. JVM은 JIT(Just In Time) 컴파일러라고 한다. 또한, JIT 컴파일러는 같은 코드를 매번 해석하지 않고, 실행할 때 컴파일을 하면서 해당 코드를 캐싱해버린다. 이후에는 바뀐 부분만 컴파일하고, 나머지는 캐싱된 코드를 사용한다. 이렇게 JIT 컴파일러는 운영체제에 맞게 바이트 실행 코드로 한 번에 변환하여 실행하기 때문에 이전의 자바 해석기(Java interpreter) 방식보다 성능이 10배 ~ 20배 정도 더 좋다.