자바 가상 머신(JVM, Java Virtual Machine) 이란?
자바 가상 머신(JVM, Java Virtual Machine)은 자바 프로그램 실행환경을 만들어주는 소프트웨어이다.
지난 포스트와 비슷한 내용이지만 과거에는 대부분의 컴퓨터 언어가 특수한 아키텍처나 OS에 맞게 컴파일되도록 설계가 되어있었다. 자바 이전에 C/C++은 모든 CPU에 맞게 컴파일될 수 있었지만 해당 CPU에 맞는 컴파일러가 필요했다. 하지만 각각의 환경을 위한 컴파일러 개발 비용은 비싸고 오랜 시간이 소요된다는 문제가 있었고 더 나은 방식을 찾기 위해 제임스 고슬링과 그 동료들은 다양한 환경의 CPU에서 실행될 수 있는 이식성이 뛰어난 Cross Platform 언어 개발에 착수하게 된다. 이렇게 자바는 "Write Once, Run Anywhere!"이라는 생각을 바탕으로 만들어졌고 한번 컴파일되어 만들어진 바이트 코드는 다양한 CPU 환경에서 실행될 수 있는 플랫폼 독립적인 특징을 가지고 있다. 그리고 이 특징을 가능하도록 만들어주는 것이 바로 자바 가상머신이다. 자바 가상 머신을 사용하면 하나의 바이트 코드(.class)를 모든 플랫폼에서 동작할 수 있기 때문에 자바 프로그램은 다양한 CPU 환경에서 이식성 문제없이 실행될 수 있도록 만들어준다.
하지만 헷갈리지 말아야할 것이 있다. 자바는 플랫폼에 종속적이지 않지만 자바 가상 머신은 플랫폼에 종속적이다. 즉 컴파일될 바이트 코드는 어떤 자바 가상 머신에서도 동작이 가능하기 때문에 플랫폼에 의존적이지 않다. 그러나 아래 그림에서 볼 수 있듯이 자바 가상 머신은 플랫폼에 의존적이고 따라서 운영체제에 자바 가상 머신을 사용해야 한다. 자바로 작성된 모든 프로그램은 자바 가상 머신에서만 실행될 수 있기 때문에 오라클은 웹 브라우저, 스마트폰, 가전기기 등에서도 자바 가상 머신을 사용할 수 있도록 지원한다.
좀 더 자세한 자바의 개발 배경에 대해서는 아래 포스팅을 참고 바랍니다.😊
자바 가상 머신(JVM, Java Virtual Machine) 의 구조
자바 가상 머신 기본 구조
자바 가상 머신은 크게 위 그림과 같이 이루어져 있다.
- 클래스 로더(Class Loader)
- 실행 엔진(Execution Engine)
- 인터프리터(Interpreter)
- JIT 컴파일러(Just-in-Time)
- 가비지 콜렉터(Garbage collector)
- 런타임 데이터 영역 (Runtime Data Area)
- JNI(Java Native Interface)
- Native Method Library
클래스 로더(Class Loader)
자바는 동적으로 클래스를 읽어오기 때문에 프로그램이 실행 중인 런타임에서야 모든 코드가 자바 가상 머신과 연결됩니다. 이렇게 동적으로 클래스를 로딩해 주는 역할을 하는 것이 바로 클래스 로더(class loader)입니다. 클래스 로더는 .class 파일을 묶어서 자바 가상 머신이 운영체제로부터 할당받은 메모리 영역인 런타임 데이터 영역으로 적재한다. 로딩, 링크, 초기화 3가지 단계를 거쳐 실행된다.
실행 엔진(Execution Engine)
클래스 로더에 의해 로딩된 클래스를 실행시키는 역할이다.
클래스 로더가 런타임 데이터 영역에 바이트 코드를 배치시키고, 실행 엔진에 바이트 코드를 실제로 JVM 내부에서 기계가 실행할 수 있는 형태로 변경한다.
- 인터프리터(Interpreter): 실행 엔진은 자바 바이트 코드를 명령어 단위로 읽어서 실행한다.
- JIT 컴파일러(Just-in-Time): 한 줄씩 수행한다는 인터프리터의 효율을 높이기 위한 컴파일러이다.
인터프리터가 반복되는 코드를 발견하면 JIT 컴파일러가 반복되는 코드를 네이티브 코드로 바꿔주고 다음부터 인터프리터는 네이티브 코드로 컴파일된 코드를 바로 사용합니다. - 가비지 콜렉터(Garbage collector): Heap 영역에서 사용되지 않는 객체들을 제거한다.
런타임 데이터 영역 (Runtime Data Area)
프로그램을 수행하기 위해 OS에서 할당받은 JVM의 메모리 영역으로 자바 애플리케이션을 실행할 때 사용되는 데이터들을 적재하는 영역이다. 메서드 영역, 힙 영역, 스택 영역, PC 레지스터, 네이티브 메서드 스택으로 구분된다.
이 포스트와 함께 작성하기엔 내용이 길어서 분리하였다. 자세히 알고 싶다면 아래 포스트 참고 부탁드립니다 ☺
JNI(Java Native Interface)
자바 애플리케이션에서 C, C++, 어셈블리어로 작성된 함수를 사용할 수 있는 방법을 제공한다. Native 키워드를 사용하여 메서드를 호출하고 대표적인 메서드는 Thread의 currentThread()이다.
Native Method Library
C, C++로 작성된 라이브러리이다.
자바 컴파일과 실행과정
자바 컴파일 과정
- 개발자가 자바 소스 코드(.java)를 작성한다.
- 자바 컴파일러가 자바 소스 코드를(.java)를 읽어 바이트 코드(.class)로 변환한다.
(이때는 컴퓨터는 읽을 수 없지만 자바 가상 머신이 읽을 수 있는 코드이다.)
자바 런타임 과정
컴파일이 완료된 후 실행되는 과정입니다.
- 컴파일된 바이트 코드(.class)를 자바 가상 머신의 클래스 로더(Class Loader)에게 전달한다.
- 클래스 로더는 동적로딩을 통해 필요한 클래스들을 로딩 및 링크하여 런타임 데이터 영역(Runtime Data Area)
즉, JVM 메모리에 바이트 코드들을 올려준다 - 실행엔진(Execution Engine)은 JVM 메모리에 올라온 바이트 코드들을 명령어 단위로 하나씩 가져와서 실행한다.
바이너리 코드, 기계어, 바이트 코드 헷갈려!🤔
- 바이너리 코드(Binary Code)
- 컴퓨터가 인식할 수 있는 0과 1로 구성된 이진코드를 의미한다. - 기계어
- 0과 1로 이루어진 바이너리 코드이다.
- 기계어가 바이너리 코드로 이루어졌을 뿐이지 모든 바이너리 코드가 기계어인 것은 아니다.즉, 바이너리 코드 != 기계어
- 기계어는 특정한 언어가 아니다. (CPU 제조사에서 CPU를 만들 때 해당 CPU에서 사용하는 명령어 집합을 공개하는데, 이것을 '기계어'라고 부를 뿐이다. 때문에 CPU가 변경되면 기계어가 달라진다. 같은 동작을 하는 명령어지만 완전히 다른 0과 1의 나열이 될 수 있다 - 바이트 코드
- CPU가 이해할 수 있는 언어가 바이너리 코드라면 바이트 코드는 가상 머신이 이해할 수 언어이다.
- CPU가 아닌 가상 머신에서 이해할 수 있는 코드를 위한 이진 표현법이다.
- Java의 가상 머신을 JVM이라고 하며 JVM을 위한 바이트 코드를 자바 바이트 코드라고 한다.
C언어는 컴파일러에 의해 소스파일(.c)이 목적파일(.obj)로 변환될 때 바이너리 파일이 된다. 이때 목적파일(.obj)은 기본적으로 컴퓨터가 인식할 수 있는 바이너리 코드의 형태지만 실행이 불가하다. 완전한 기계어가 아니기 때문이다. 변환된 목적파일(.obj)은 링커에 의해 실행 가능한 실행파일(.exe)로 변환될 때 함수나 헤더 파일 등의 실제 메모리 주소를 코드에 반영하는 과정에서 일부 주소값이 변경되는데 이 과정을 거쳐야 비로소 컴퓨터가 바로 실행 가능한 100% 기계어가 된다.즉, 목적파일은 바이너리 코드지만 컴퓨터가 바로 실행가능한 100% 기계어라고 할 수 없고 완벽한 100% 기계어는 실행파일이다(.exe)
Java에서는 컴파일러(javac)에 의해 소스파일(.java)이 목적파일(.class)로 변환될 때 컴퓨터가 바로 인식 가능한 바이너리 코드가 아닌 바이트 코드로 변환된다. 그 이유는 자바 가상 머신에 의해 실행되기 때문이다.
글을 마치며
여러 번 읽다 보니 이제 대충은 알지만 여전히 자세하기 설명하긴 어렵고 공부할 내용이 끝이 없는 JVM! 아주 가깝고도 먼 느낌이다. 그래도 가까운 느낌이라도 있는 게 불행 중 다행이 아닐까 ㅎㅎ 사실 오랜 기간 동안 역사에 이름을 남긴 개발자들이 발전시킨 기술을 내가 한 번에 이해하는 게 더 말도 안 될 것 같긴 하다😂 차근차근 쌓아간다는 생각으로 공부하다 보면 어느새 더 나아진 날 발견하지 않을까 싶다. 오늘도 파이팅!
참고 자료 및 사이트