본문 바로가기
생각정리

[기술면접 시리즈] 자바의 컴파일 과정(JVM 메모리 구조)

by 에드박 2020. 8. 3.

 

자바의 컴파일 과정은 클래스 파일을 생성하고 JVM 에서 바이트코드를 기계어로 변환해 메모리상에 배치되어 우리가 만든 코드를 수행합니다. 라고 설명할 수 있지만 아직 조금 부족한것 같습니다. JVM을 이해하면 자바의 메모리 구조에 대해 알고 메모리를 효율적으로 사용할 수 있게됩니다. 그래서 좀 더 자세히 정리해서 적어보겠습니다.

 


JVM이란?

 

자바언어는 운영체제에 독립적입니다. 기존의 언어는 운영체제에 맞게 개발된 프로그램을 다른 종류의 운영체제에 적용하려면 많은 노력이 필요합니다. 하지만 자바는 Java 애플리케이션과 OS(운영체제 Window, Linux, Macintosh 등) 사이에 JVM(자바가상머신)이 .java의 바이트 코드를 해당 운영체제가 이해할 수 있는 기계어로 변환하여 전달합니다.

JVM 은 Window, Linux, Macintosh 주요 OS 용 JVM 이 있습니다. 이 말은 JVM 만 OS에 맞게 바꿔주면 재사용 가능하다는 것입니다. 그리고 가장 중요한 자동 메모리 관리(Garbage Collection) 을 수행합니다. JVM 은 스택기반의 가상머신 입니다.

 


 

자바의 컴파일 과정

  1. 자바의 프로그램이 실행되면 JVM 은 OS로 부터 이 프로그램이 필요로 하는 메모리를 요청하여 할당받습니다. JVM은 이 메모리를 용도에 따라 여러 영역으로 나누어 관리합니다.
  2. 자바 컴파일러가(javac) 자바 소스코드를(.java) 를 읽어들여 바이트코드(.class) 로 변환(아직 컴퓨터가 이해할 수 없는 상태입니다. 바이트 코드는 인간이 보기 편한 형태입니다.)시킵니다.
  3. Class Loader 를 통해 class 파일들을 JVM 내부로 로딩합니다.
  4. 로딩된 class 파일들은 실행 엔진(Execute Engine)을 통해 기계어(컴퓨터가 알아들을 수 있는 언어)로 해석됩니다.
  5. 해석된 바이트 코드는 메모리 상(Runtime Data Area)에 배치되어 실질적인 수행을 하게됩니다.
  6. 이런 실행과정 중에 JVM은 Thread Synchronization 이나 GC(Garbage Collection) 같은 작업을 수행하게 됩니다.

 


JVM의 구조

클래스 로더(Class Loader)

- JVM 내로 클래스(.class)파일을 로드하고, 링크를 통해 배치하는 작업을 수행합니다. Runtime 시에 동적으로 클래스를 로드합니다. .jar 파일 내  저장된 클래스들을 JVM 위에 탑재하고 사용하지 않는 클래스들은 메모리에서 삭제합니다.

자바는 동적 로딩(Dynamic Loading) 을 지원합니다. 따라서 컴파일 타임에 모든 클래스가 로딩되지 않고 필요한 시점에 (런타임 도중) 해당 클래스를 실행하고 로딩합니다. 그 역할을 Class Loader 가 하는것입니다.

 

실행 엔진(Execution Engine)

- 클래스를 실행시키는 역할 입니다. 클래스 로더에 의해 JVM 내에 로딩된 클래스 파일(바이트 코드)은 실행 엔진에 의해 실행되어 컴퓨터가 이해할 수 있는 기계어로 변환합니다.

 

인터프리터(Interpreter)

- 실행 엔진은 자바 바이트 코드를 명령어 단위로 읽어서 해석합니다. 이는 자바의 단점으로 한줄 씩 읽어서 속도가 느립니다.

 

JIT(Just - In - Time)

- 인터프리터의 단점을 보완하기 위해 도입된 JIT 입니다. 인터프리터로 실행하다가 적절한 시점에 바이트코드 전체를 컴파일 하여 기계어(네이티브 코드)로 변경 캐시에 저장합니다. 캐시에 보관된 네이티브 코드를 사용하기 때문에 한 번 컴파일 된 후에는 빠르게 수행할 수 있습니다. 하지만 JIT로 컴파일 하는 과정이 당연히 인터프리팅 하는것보다 느리기 때문에 한번만 실행되는 코드라면 인터프리팅 하는것이 유용합니다.

 

가비지 컬렉터(Garbage Collector)

- GC를 수행하는 모듈(쓰레드) 가 있습니다.

 

 

메모리 상 공간(Runtime Data Area)

- JVM 이 OS 로 부터 할당받은 메모리 공간입니다.

 

PC Register

- Thread 가 시작될 때 함께 생성되는 공간으로 스레드 마다 하나씩 존재합니다. 스레드가 어떤 부분을 어떤 명령으로 실행해야 할 지에 대한것을 기록하는 부분으로 현재 수행중인 JVM 의 명령 주소를 가집니다 .

 

JVM 스택 영역(JVM Stack)

- 프로그램 실행과정에서 임시로 할당되었다가 메소드를 빠져나가면 바로 소멸되는 특성의 데이터를 저장하기 위한 영역입니다. 각종 형태의 변수나 임시 데이터, 스레드나 메서드의 정보를 저장합니다. 메소드 호출 시마다 각각의 스택 프레임(그 메서드만을 위한 공간)이 생성됩니다. 이곳에 메서드 안에서 사용되는 값들을 저장합니다. 호출된 매개변수, 지역변수, 리턴 값 및 연산 시 변화하는 값들을 임시로 저장합니다. 메서드 수행이 끝나면 프레임 별로 삭제합니다.

 

Native Method Stack

- 자바 프로그램이 컴파일 되어 생성되는 바이트 코드가 아닌 실제 실행할 수 있는 기계어로 작성된 프로그램을 실행시키는 영역입니다. JAVA 가 아닌 다른 언어로 작성된 코드를 위한 공간입니다. JAVA Native interface 로 인해 바이트코드로 변환하여 저장됩니다. 일반 프로그램 처럼 커널이 스택을 잡아 독자적으로 프로그램을 실행시키는 영역입니다.

 

Method Area (= Class Area = Static Area )

- 클래스 정보를 처음 메모리 공간에 올릴 때 초기화되는 대상을 저장하기 위한 공간입니다. 올라가게 되는 메서드의 바이트 코드는 프로그램의 흐름을 구성하는 바이트 코드입니다. 자바는 main 메서드로 부터 객체 생성 메소드 호출의 흐름을 이어가기 때문에 거의 모든 바이트 코드가 올라간다고 보면 됩니다.

이 공간에는 Runtime Constant Pool 이라는 공간이 존재하는 데 이는 상수 자료형을 저장하여 참조하고 중복을 막기위한 공간입니다.

- 올라가는 정보의 종류

  • 멤버변수, 메서드에 대한 정보 (이름, 타입 접근제어자 등등)
  • class 인지 interface 인지의 여부 저장, Type 의 속성 , 전체 이름, super class 의 전체이름(interface 이거나 object인 경우 제외)

 

Method Area 는 클래스를 위한 공간이라면 Heap 영역은 객체를 위한 공간입니다.

Heap 과 마찬가지로 Method Area 도 GC의 관리 대상이 됩니다.

 

 

Heap 영역

- 객체를 저장하는 가상 메모리 공간입니다. new 연산자로 생성된 객체와 배열을 저장합니다. 물론 Class Area 에 올라온 클래스들만 객체로 생성할 수 있습니다. 

 

Permanent 영역

- 생성된 객체들의 주소값이 저장된 공간입니다. Class Loader 에 의해 로드되는 Class, Method 등에 대한 Meta 데이터가 저장되는 영역이고 JVM에 의해 사용됩니다. Reflection을 사용해 동적으로 클래스가 로딩 되는 경우에 사용합니다. 내부적으로 Reflection 을 자주 사용하는 Spring Framework를 사용한다면 이 영역에 대한 고려가 필요합니다.

 

Java Reflection 에 관한 글
https://madplay.github.io/post/java-reflection
JAVA8 부터는 Permanent 영역대신 Metaspcae 로 호출되는 네이티브 영역에 저장됩니다. 
자세한 내용은 아래 링크를 참고해주세요!
https://johngrib.github.io/wiki/java8-why-permgen-removed/

 

New / Young 영역

- Eden : 객체들이 최초로 생성되는 공간입니다.

- Survivor 0 / 1 : Eden 에 의해 참조되는 객체들이 저장되는 공간입니다.

 

Old 영역

- New / Young 영역에서 일정 시간 참조되고 난 뒤, 살아남은 객체들이 저장되는 공간입니다. 

Eden 에서 객체가 가득 차면 첫 번째 GC (minor GC) 가 발생하고 Eden 영역에 있는 값들을

Survivor 1 영역에 복사하고 이 영역을 제외한 나머지 영역의 객체들을 삭제합니다. 

 

인스턴스는 소멸 방법과 소멸 시점이 지역 변수와는 다르기 때문에 힙이라는 별도의 영역에 할당됩니다.

JVM 은 매우 합리적으로 더이상 인스턴스가 존재할 필요가 없을 때 인스턴스를 소멸시킵니다.

 

 

Garbage Collector 에 대해서도 조만간 공부해서 글을 쓰도록 하겠습니다.

 

 

참고한 블로그 글입니다.
https://asfirstalways.tistory.com/158

댓글