자바 가상 머신(Java Virtual Machine)은 마치 자바의 정체성과도 같고
동시대의 다른 언어들과 자바를 구분짓는 주요한 요인이었습니다.
자바 가상 머신은 우리가 작성한 자바 소스를 실행시키기 위해
실행되는 프로세스를 의미합니다.
자바가 탄생했던 시기의 프로그래밍 언어들은, 컴파일러를 가지고 있었지만
환경마다 다른 컴파일러가 필요했습니다. 하지만 자바는 개발자가 작성한
자바 소스 코드
가 자바 컴파일러에 의해서 자바 바이트 코드
로 변환되고,
이렇게 변환된 바이트 코드는 JVM
위에서 각각의 환경에 맞는
기계어
로 번역되어 하드웨어 및 OS에서 실행됩니다.
이렇게 자바는 OS에 종속되지 않는 프로그래밍 언어를 위해 디자인되었으며
현재까지도 이러한 이점이 가장 크기 때문에 많은 곳에서 자바를 채택하여
사용하고 있습니다.
JVM은 크게 Class Loader
Runtime Data Areas
Excution Engine
의
세 영역으로 나누어져 있습니다. 각각의 기능에 대해 설명하겠습니다.
클래스 로더는 자바 프로그램이 실행되는 RunTime 시점에 자바 컴파일러가 번역한
자바 바이트 코드를 읽어, 할당받은 메모리에 배치하는 작업을 수행하는데
크게 세 가지 동작을 수행합니다.
클래스 로더의 실행 방식에 관해서는 어려운 부분이 있어서, 몇 가지의 특징만
정리하도록 하겠습니다.
계층적 구조
클래스 로더는 한 가지가 아니고, 여러 가지로 나누어져 있으며 이들은 계층적
특성을 가집니다. 가장 상위에는 Bootstrap Class Loader가 위치합니다.
로딩 요청 위임
계층적 구조 특성과 함께하는 특징이며, 동적 클래스 요청이 들어올 때
클래스 요청은 상위 클래스 로더부터 권한을 가집니다.
상위 클래스 로더가 이러한 클래스를 찾지 못했을 때 계층을 내려가면서
클래스를 찾습니다.
가시성 제약 조건
부모 클래스 로더가 찾지 못하는 클래스는, 자식 클래스 로더가 찾을 수 없습니다.
따라서 최상위 로더가 찾지 못하는 Class는 ClassNotFoundException을 발생시킵니다.
클래스 언로드 불가
로딩된 클래스는 클래스 로더에 의해서 언로드될 수 없습니다.
런타임 데이터 공간은, JVM이 프로세스 실행을 위해 OS로부터 할당받은 메모리 공간을 의미합니다.
이 공간은 5가지 영역으로 분할되어 사용됩니다. 이 공간은 모든 Thread가 공유하는 공간과
Thread 별로 개별적인 공간으로 분리됩니다. 먼저 모든 Thread가 공유하는 공간을 알아보자면
Method Area
Class Loader가 읽어온 클래스와, 데이터 타입, 파라미터, 접근 제어자와 같은 메소드에
대한 정보와 static으로 선언된 변수, 상수 들이 저장되어있는 공간입니다.
이 공간은 JVM 시작시에 생성되며 프로그램 종료 시까지 유지되는 공간입니다.
위에서 로드된 클래스는 언로드 될 수 없다고 언급했던 이유가 여기에도 나타나 있습니다.
Heap Area
런타임에서 동적으로 할당되어 사용되는 공간입니다. 우리가 흔히 쓰는 new 키워드를 사용하면
이 영역에 할당되며, 모든 참조 타입 인스턴스는 이 공간에 할당된다고 생각하면 됩니다.
Heap 영역과 GC(Garbage Collection)은 다음에 더 자세하게 다루도록 하겠습니다.
다음은 Thread들이 개별적으로 사용하는 공간입니다. Thread의 특성상,
하나의 Thread는 다른 Thread의 개별 영역을 참조할 수 없습니다.
Stack Area
메소드 호출 시 생성되는 쓰레드 수행정보를 Frame 형태로 기록하여 저장합니다.
연산 시 발생하는 지역변수, 매개변수 등 임시 데이터를 저장합니다.
PC 레지스터
현재 실행 중인 JVM의 주소를 가지고 있습니다. CPU의 Instruction을 수행하는 공간입니다.
Native Method Stack Area
자바 외의 언어(C/C++)로 작성된 코드를 위한 메모리 영역입니다. JNI(Java Native Interface)를
통해 호출된 코드를 수행하며 JVM 내부에 영향을 주지 않기 위해 사용되는 영역입니다.
실행 엔진(Execution Engine)은 클래스 로더에 의해 적재된 바이트 코드들을 기계어로 번역해
Instruction 단위로 실행하는 엔진입니다. 명령어를 한줄한줄 번역하여 실행하는
Interpreter 방식
과 JIT(Just in Time) Compiler
을 이용하는 방식이 있습니다.
위에서 말했듯이, 일반적으로 명령어을 한줄한줄 번역하여 실행하는 방식입니다.
대부분의 인터프리터 방식이 그렇듯이 번역을 계속 하면서 실행하기 때문에
실행 속도가 굉장히 속도가 느리다는 단점이 있습니다.
자바의 실행 성능을 끌여올려준 방식입니다. 이름은 컴파일러지만 인터프리터방식과 같이
실행시에 적용되는 방식이며 인터프리터 방식과 정적(static)방식을 혼합한 형태입니다.
변환 작업은 인터프리터에 의해 계속 수행되지만, 필요한 정보를 캐시에 담아두었다가
재사용하게 됩니다.
한 번만 실행되고 끝나버리는 코드라면, 인터프리터 방식보다 JIT Compiler방식이 불리하지만
코드의 재사용성을 높히는 디자인 패턴을 적용한다면 성능을 훨씬 끌어올릴 수 있습니다 !
JVM은 자바의 상징과도 같은 프로세스입니다. 또한 런타임에서 실행되는 Garbage Collection도 있지만
GC는 자바에서 굉장히 중요한 부분이므로 동작 원리에 대해서 다음 포스팅에 자세히
작성하겠습니다.