JVM 구조와 자바의 실행방식

JVM의 구조와 자바의 실행방식에 대해 정리하는 글입니다.

JVM의 구성과 자바의 실행방식

JVM의 구조

JVM 크게 4가지로 구성된다고 할 수 있다.

  • Class Loader
  • Execution Engine
  • Garbage Collector
  • Runtime Data Area

자바의 실행방식

위에 그림에서 보이는 것처럼 자바 애플리케이션의 실행에서 Java 컴파일러가 먼저 동작을 수행한다.

Java 컴파일러는 .java 파일을 모두 JVM이 사용할 수 있는 .class 파일로 컴파일 한다. 이 시점이 컴파일 타임이다.

이후 JVM을 실행하면서 런타임 시점이 시작된다.

JVM 내부에서는 애플리케이션을 실행하기 위해 Execution Engine이 필요한 클래스들을 Class Loader에 요청하고 Class Loader가 바이트코드의 .class에서 가져와 메모리에 올린다. (로컬 디스크에서 .class 파일을 가져올 수도 있지만 네트워크를 통해서 가져올 수 있다. - URLClassLoader)

가져오는 클래스들의 바이트 코드들이 이상이 없는지, 자바의 보안 규칙을 위배하지 않는지 검사를 한다. (자바는 네트워크를 통하여 전송된 자바 프로그램이 컴퓨터를 훼손시키는 것을 방지 하기 위해 엄격한 보안 규칙을 갖고있다.)

그 다음으로 Execution Engine이 메모리에 올라온 바이트코드를 실행하면서 애플리케이션이 실행된다. 계속 이러한 동작을 하면서 애플리케이션이 동작한다.

Class Loader

자바 애플리케이션이 실행되기 이전 Java 컴파일러는 Java 소스 파일을 .class 파일(바이트 코드)로 컴파일해준다. 그 이후 실행하면서 필요한 바이트 코드 파일들을 메모리에 올려야 하는데 이 일을 하는 것이 Class Loader이다.

즉, Class Loader 는 class 파일을 JVM이 운영체제로부터 할당받은 메모리 영역 (Runtime Data Area)에 적재하는 역할을 한다.

Execution Engine

Class Loader에 의해 메모리에 적재된 바이크 코드의 클래스들을 기계어로 변경해 명령어 단위(Operation Code)로 실행하는 역할을 한다.

자바는 인터프리터 방식을 사용하는데, 인터프리터 방식은 한줄 한줄 읽어들여야 하는 방식이여서 컴파일 방식에 비해 느리다. 그래서 자바는 컴파일과 인터프리터 방식을 모두 사용한다. 바로 JIT 컴파일러이다.

JIT 컴파일러는 처음 바이트 코드를 읽을 때에는 인터프리터 방식으로 한줄 한줄 씩 읽고 반복되는 바이트 코드를 또 읽게되면 캐싱해둔 곳에서 이미 기계어로 바꾼 내용을 가져다가 쓴다.

Garbage Collector

Garbage Collector는 Heap 메모리 영역에 생성된 객체들 중에 참조되지 않은 객체들을 제거하는 역할을 한다.

GC(Garbage Collection) 의 발생은 일정하게 정해져 있지 않기 때문에 언제 객체를 정리할지는 알 수 없다. 즉 바로 참조가 없어지자마자 작동하는 것이 아니다.

🔗 GC의 자세한 동작을 참고하길 바란다.

Runtime Data Area

JVM의 메모리 영역으로 Java 애플리케이션 실행시 사용되는 데이터를 적재하는 영역이다. 크게 5가지 영역으로 구분된다.

  • Heap
  • Method
  • Stack
  • PC register
  • Native Method Stack

Heap

  • 인스턴스화 된 모든 클래스 인스턴스와 배열을 저장을 하는 공간이다.
  • 모든 JVM 스레드에 공유되는 공유 자원이기도 하다.
  • Heap에 저장된 할당된 메모리 회수 권한은 무조건 가비지 컬렉터에 의해서만 회수가 가능하다.

Method

  • 클래스 수준의 정보를 저장하는 공간이다.
    • 타입 정보
    • 필드 정보(필드 타입, 필드 수정자)
    • 메서드 정보(메서드의 메타 데이터)
    • 런타임 상수 풀
    • 클래스 변수
  • 모든 JVM 스레드에 공유되는 공유 자원이다.
  • 사실 논리적으로 Heap 영역에 포함되는 영역이다.

PC register

  • 쓰래드가 생성될때마다 생성되는 영역으로 Program Counter 즉, 현재 쓰레드가 실행되는 부분의 주소와 명령을 저장하고 있는 영역이다. 이것을 이용해서 여러 쓰레드를 제어한다.
  • JVM은 Stacks-Base 방식으로 작동한다. CPU에 직접 Instruction을 수행하지 않고 Stack에서 Operand를 뽑아내 이를 별도의 메모리 공간에 저장하는 방식을 취한다. 이러한 메모리 공간을 PC register라고 한다.

Native Method Stack

  • 자바 외 언어로 작성된 네이티브 코드를 위한 메모리 영역이다. 보통 C/C++ 등의 코드를 수행하기 위한 스택이다.
  • JNI를 통해 표준에 가까운 방식으로 구현이 가능하다.

Reference

https://doohong.github.io/2018/03/02/Java-runtime-data-area/

https://velog.io/@hono2030/JVM%EC%9D%98-%EA%B5%AC%EC%A1%B0

https://j4bez.tistory.com/14

https://catch-me-java.tistory.com/11

⤧  Next post 메시지 큐와 종류 그리고 비교 ⤧  Previous post HashMap vs Hashtable