어 나 갱수.

[Java] JVM 이란 ? 🤠 본문

Java

[Java] JVM 이란 ? 🤠

김경수 2024. 1. 7. 17:30
728x90

오늘은 JVM에 대해 알아보겠습니다. 평소에 Java나 Kotlin으로 개발을 많이 하는데 정작 그 Java와 Kotlin이 어떻게 돌아가고 우리가 작성한 코드들이 어떻게 실행되는지에 대한 동작 원리들을 따로 공부해 본 적 없어서 오늘 한 번 알아보는 시간을 가져보겠습니다.

JVM 이란?

먼저 JVM이 뭔지 알아보겠습니다. JVM은 Java Virtual Machine의 약자로, 자바 가상 머신이라고 불립니다.

자바 가상 머신이란 자바 프로그램 실행 환경을 만들어주는 소프트웨어입니다.

개발자가 .java 파일의 자바 코드를 컴파일하면 컴파일러에서 .class의 코드로 변환을 해주면 이 바이트코드가 자바 가상 머신에서 실행됩니다.

Java의 장점

Java의 장점은 어떠한 OS(운영체제)에서 실행 가능한 언어라는 것입니다.

JVM을 사용함으로써 얻는 가장 큰 이점이 무엇일까요? JVM을 사용하면 바이트코드(.class)로 어떠한 OS에서도 동작가능한 코드로 변환시켜 주기 때문입니다.

 

JVM을 더 설명하자면 바이트코드를 어떠한 운영체제에서ㄷ 실행가능한 코드로 번역해 주는 번역기 같은 역할이라고 생각하면 좋을 거 같습니다.

 

위의 사진은 Java 프로그램의 실행 과정입니다.

개발자가 자바 코드를 실행시키면 자바 컴파일러에서 자바코드(.java)를 바이트코드(.class)로 변환해 줍니다.

변환된 바이트코드를 JVM에서 실행을 시키면 JVM에서는 각 운영체제에 맞는 코드로 변환하여 어떠한 운영체제라도 실행 가능하도록 해주는 역할을 합니다. 만약 어떠한 운영체제에서 자바 코드를 실행시키고 싶다 하면 각 운영체제에 맞는 JVM만 설치한다면 어떤 운영체제라도 자바 프로그램을 실행시킬 수 있습니다.

 

JVM의 구조

위에서는 자바의 실행 과정을 알아보았습니다. 이제부터는 JVM이 어떻게 동작을 하고 구조가 어떤지 알아보겠습니다.

 

  1. 자바 프로그램을 실행하면 JVM은 운영체제로부터 메모리를 할당받는다.
  2. 자바 컴파일러가 자바코드를 바이트코드로 컴파일시킨다.
  3. Class Loader를 통해 JVM Runtime Data Area에 로딩한다.
  4. Runtime Data Area에 로딩된 바이트코드는 Execution Engine의 Interpreter와 JIT Complier를 통해 해석됩니다.
  5. 해석된 바이트코드는 Runtime Data Area에 각 영역에 배치되어 실질적인 수행이 이루 진다.

Class Loader

클래스 로더는 자바 바이트코드(.class)를 읽어서 JVM의 실행 엔진이 사용할 수 있도록, Runtime Data Area(JVM Memory)의

메서드 영역에 적재하는 역할을 한다. 

위의 사진은 클래스 로더의 자세한 실행과정이다.

 

로딩

바이트코드를 바이너리 코드로 바꾸고 이를 메서드 영역(Method Area)에 저장하는 과정이다.

바이너리 코드를 만들 때 코드에 저장하는 데이터는 다음과 같다.

  • Fully-Quailified Class Name(FQCN)
  • Class, Interface, Enum을 구분하여 저장
  • 메서드와 변수

로딩이 끝나면 해당 클래스 타입의 객체를 생성해서 힙 영역에 저장한다.

 

링크

클래스 파일을 사용하기 위해 검증하는 과정이다.

  • Verifying(검증) : 읽어드린 클래스 파일이 유효한지 검증하는 과정이다. 클래스 파일이 JVM의 구동 조건대로 구현되지 않을 경우 VerifyError를 던진다.
  • Prepareing(준비) : 클래스가 필요로 하는 메모리를 할당한다.
  • Resolving(분석) : 클래스의 상수 풀 내 모든 씸볼릭 레퍼런스를 디렉트 레퍼런스로 변경한다.

초기화

클래스 변수들을 적절한 값으로 초기화시킨다.

 

실행 엔진(Execution Engine)

실행 엔진은 클래스 로더를 통해 런타입 데이터 영역에 배치된 바이트코드를 읽어서 실행하는 역할입니다.

자바 바이트코드(.class)는 기계가 바로 이해할 수 있는 언어보다는 JVM(가상 머신)이 이해할 수 있는 언어입니다.

이제 실행엔진에서 JVM만 이해할 수 있는 바이트코드를 기계도 이해할 수 있는 언어로 형태로 변경해 줍니다.

 

이 과정에서 실행 엔진은 인터프리터와 JIT 컴파일러 두 가지 방식을 혼합하여 바이트코드를 실행한다.

 

인터프리터

바이트 코드를 명령어 하나씩 읽어서 바로 실행시킨다. 이렇게 순차적으로 실행을 하니 단점이 하나 발생합니다.

동일한 코드가 있어서 하나하나씩 다 실행시키기 때문에 속도가 조금 느릴 수 있습니다.

 

JIT(Just In Time)

JIT은 위에서 말씀드린 인터프리터의 단점인 속도와 성능을 보완하고자 나온 방법입니다.

인터프리터는 코드를 명령어 하나씩 읽어서 바로 실행시키는데 JIT은 기계어로 바꾸는 과정에서 캐싱을 하고 기계어로 바꾸면서 비슷한 부분이 있으면 캐싱되어 있는 코드를 불러와서 사용하는 방식으로 이루어집니다.

int a = 19;
Display(a);
Display(a);
Display(a);

위에 코드로 예시를 들어보겠습니다.

위에 코드를 인터프리터 방식으로 진행하였다면 Display(a) 함수를 실행시키는 행위를 3번이나 해야 했다면, JIT 방식으로 진행하면

처음 호출되는 Display(a) 만 실행되고 나머지는 캐싱된 코드를 불러와서 실행하기 때문에 인터프리터 방식보다 속도도 빠르고 효율도 더 좋다는 것을 알 수 있습니다.

 

Runtime Data Area

JVM에서 클래스로더로부터 받은 바이트코드들을 저장하는 곳입니다.

Method Area

Method Area에는 클래스와 관련된 모든 정보가 저장됩니다. 클래스와 관련된 정보는 중요한 정보이기에 JVM에는 하나의 Method Area만 존재합니다.

 

Heap Area

Heap 영역은 메서드 영역과 함께 모든 스레드가 공유하며, JVM이 관리하는 프로그램 상에서 데이터를 저장하기 위해 런타임 시 동적으로 할당하여 사용하는 영역이다.

 

Stack Area

스택 영역은 int, long, boolean 등 기본 자료형을 생성할 때 저장하는 공간으로, 임시적으로 사용되는 변수나 정보들이 저장되는 영역이다. LIFO 구조로 push와 pop기능 사용방식으로 동작한다.

 

PC Register

Thread가 시작될 때 생성되는 공간으로, 스레드마다 하나씩 존재한다.

Thread가 어떤 부분을 어떤 명령으로 실행해야 할 지에 대한 기록을 하는 부분으로 현재 수행 중인 JVM 명령의 주소를 갖는다.

Garbage Collection

프로그램을 개발하다 보면 필요하지 않은 메모리인 Garbage들이 생기게 된다.

C언어에서는 Garbage를 free()라는 함수를 통해 직접 지워준다. 하지만 Java나 Kotlin을 이용해서 개발하는 개발자는 직접 메모리를 관리하지 않는다. 그 이유가 Garbage Collection 덕분이다. Garbage Collection이 직접 불필요한 메모리를 알아서 정리해 주기 때문이다.

 

 

 

728x90