자바에서 사용되는 JVM(Java Virtual Machine)의 역할에 대해서 살펴보고 왜 필요한 것인지 알아보려고 한다.
JVM은 운영체제(OS)와 JAVA 언어간의 중개자 역할을 하면서 어떤 운영체제를 사용하더라도 같은 JAVA 코드를 이용해서 실행 할 수 있도록 도와준다. 또한 가장 중요한 메모리 관리를 담당하므로써 개발자가 일일히 객체의 할당된 메모리를 해제하지 않아도 Garbage Collector가 Garbage Collection을 수행한다.
가상 머신의 종류에는 스택 기반 가상머신과 레지스터 기반 가상머신이 존재하는데, X86 및 ARM 아키텍처에서 사용되는 레지스터 기반 가상머신과 달리 JVM은 스택 기반 가상머신에 속한다. 이에 대한 내용은 차후에 설명하도록 하겠다.
앞서 말한 JVM의 실행 과정이다. 이제 JVM 위에서 자바 프로그램이 어떻게 수행이 되는지 알아보자.
JAVA compiler
는 우리가 만든 .java
파일을 컴파일해 바이트코드로 만들고 이를 .class
파일에 저장하는 역할을 한다.
Class Loader
는 저장된 .class
파일들을 읽어와 운영체제로부터 할당받는 메모리 공간인 Runtime Data Areas
에 적재하는 역할을 한다.
이러한 역할은 자바 어플리케이션이 실행 될 때만 수행된다.
Execution Engine(실행 엔진)
은 Class Loader
에 의해 Runtime Data Areas
에 적재된 바이트 코드화 된 클래스들을 기계어로 변경해 명령어 단위로 실행하는 역할을 한다.
바이트 코드는 기계가 바로 수행하기 보다는 비교적 사람이 보기 쉬운 형태의 고급언어에 가깝기 때문에 JVM 내부에서 기계가 쉽게 일을 수행할 수 있도록 2가지 방법을 이용하여 바이트 코드를 변경한다.
Execution Engine(실행 엔진)
은 바이트코드를 명령어 단위 별로 읽어와서 실행한다. 하지만 인터프리터의 단점을 그대로 가지고 있기 때문에 한 줄 씩 수행하고, 그 때문에 속도가 느리다는 단점을 가지고 있다.
JIT compiler
는 이러한 인터프리터의 단점을 고치기 위해서 도입된 컴파일러이다. 적절한 시간에 전체 바이트 코드를 네이티브 코드로 변경해서 Execution Engine이 네이티브로 컴파일된 코드를 실행하는 방식으로 되어 성능을 높일 수 있다.
Runtime Data Area
는 JVM에서 메모리에 존재하는 영역으로 자바 어플리케이션을 실행할 때 사용되는 데이터들을 적재하는 영역이다.
이 영역은 크게 Method Area, Heap Area, Stack Area, PC Register, Native Method Stack으로 나눌 수 있다.
클래스와 인터페이스에 존재한 상수 풀 및 멤버 변수, 클래스 변수(Static 변수), 생성자 및 메소드를 저장하는 공간이다.
Heap
영역은 JVM에서 런타임시 동적으로 할당하여 사용하는 영역으로서 New
연산자로 생성된 객체 또는 객체와 배열을 저장한다. 또한 참조하는 변수, 필드가 없다면 제거 대상이 되는 객체로 GC의 대상이 된다.
여기서 Young Generation
과 Old Generation
영역은 자바 어플리케이션 단에서 사용하는 부분이다.
기존에는 Permanent 영역
이 존재하였지만 JAVA8
이후로는 이 부분이 사라지고 Native 영역에 Metaspace 영역이 생겼다.
쓰레드 마다 스택이 하나 씩 존재하며, 스레드가 시작 될 때 할당된다. 선입 후출(LIFO) 구조로 push, pop 기능을 사용한다. 원시타입(Primitive type) 변수는 스택 영역에 직접적인 값을 가진다.
현재 수행 중인 JVM의 명령 주소를 갖는 부분으로 메모리에 데이터 전달 전에 저장 한다.
Java가 아닌 언어로 작성된 네이티브 코드들을 저장하기 위한 Stack이다. JNI, JNA 모두 이 부분을 이용하여 C/C++ 코드를 수행하기 위해 필요하다. 또한 네이티브 메소드의 매개변수, 지역변수 등을 바이트 코드로 저장한다.