JVM 개념정리

jeeeny·2024년 10월 28일
2

📝 공부기록

목록 보기
2/2

JAVA의 컴파일과 실행

컴퓨터는 우리가 작성한 소스코드를 이해하지 못한다. 컴퓨터가 이해할 수 있는 언어는 기계어이며 0과 1로 이루어져 있다. 그래서 우리가 작성한 소스코드를 컴퓨터가 이해할 수 있는 기계어로 변환해야 한다.

  • 프로그램이 실행되면 JVM은 OS로부터 이 프로그램이 필요로 하는 메모리를 할당받는다.
  • 자바 파일(.java)이 자바 컴파일러에 의해 자바 바이트 코드(.class)로 변환된다.
  • Class Loader를 통해 자바 바이트 코드를 JVM으로 필요한 시점에 로딩한다.
  • 해석된 바이트 코드는 Runtime Data Area에 배치되어 Execution Engine에 의해 실질적인 수행이 이루어지게 된다.
  • 실행 과정 속에서 JVM은 필요에 따라 GC와 같은 관리 작업을 수행한다.

JVM 이란 무엇인가

JVM은 Java Virtual Machine의 약자로 자바를 실행시키기 위한 가상 머신이다. 자바 소스코드를 실행시키기 위해 컴퓨터 속에 존재하는 또 다른 컴퓨터 정도로 이해할 수 있다. 위에서 보았듯 우리가 자바로 작성한 소스코드(.java)는 Java Byte Code(.class) 로 변환되어 JVM 에 의해 해석되고 실행된다.

자바의 가장 큰 특징은 운영체제에 독립적(WORA)이라는 것이다. JVM 덕분에 자바는 운영체제에 상관없이 어떤 환경에서도 동일한 모습으로 실행 가능하다.

C 언어는 운영체제에 따라 다른 목적파일(기계어)를 생성하지만 자바는 목적파일(바이트코드)를 만들어 JVM이 설치된 어떤 컴퓨터에서도 실행시킬 수 있다. 자바는 네트워크에 연결된 모든 디바이스에서 작동하는 것이 목적이었다. 따라서 디바이스마다 운영체제나 하드웨어가 다르기 때문에 자연스럽게 플랫폼에 의존하지 않도록 언어를 설계했다.

자바 코드는 컴퓨터에서 완전히 컴파일된 상태가 아니고 실행시 JVM을 통해 해석되기 때문에 속도가 느리다는 단점이 있다. 요즘엔 바이트코드를 기계어로 바로 변환해주는 JIT 컴파일러와 향상된 최적화 기술이 적용되어 속도 문제는 상당히 개선되었다.

JVM 메모리 구조

1. Class Loader

자바 클래스들은 시작시 한번에 로드되지 않고 애플리케이션에서 필요할 때 로드된다. 클래스 로더는 런타임에 클래스를 동적으로 JVM에 로드하는 역할을 수행한다. 자바의 클래스들은 자바 프로세스가 새로 초기화되면 클래스 로더가 차례차례 로딩되며 작동한다.

Bootstrap Class Loader

JVM 시작 시 최초로 실행되는 클래스 로더이다. 부트스트랩 클래스 로더는 자바 클래스를 로드하는 것이 아니라 자바 클래스를 로드할 수 있는 자바 자체의 클래스 로더와 최소한의 자바 클래스(java.lang.Object, Class, ClassLoader)만을 로드한다.

Extension Class Loader

부트스트랩 클래스 로더를 부모로 갖는 클래스 로더로서, 확장 자바 클래스들을 로드한다. java.ext.dirs 환경 변수에 설정된 디렉토리의 클래스 파일을 로드하고, 이 값이 설정되어 있지 않은 경우 ${JAVA_HOME}/jre/lib/ext 에 있는 클래스 파일을 로드한다.

System Class Loader

자바 프로그램 실행 시 지정한 Classpath에 있는 클래스 파일 혹은 jar에 속한 클래스들을 로드한다. 쉽게 말하자면 우리가 만든 .class 확장자 파일을 로드한다.

2. Runtime Data Area

JVM이 프로그램을 실행하기 위해 운영체제로부터 할당받은 메모리 공간이다. 클래스 로더가 배치한 데이터들을 보관하는 저장소로 5가지 구성요소로 나뉘어져 있다. 이중 Method, Heap 영역은 모든 스레드가 공유하고 나머지는 스레마다 하나씩 생성된다.

Method(Static) Area

클래스와 인터페이스의 메타데이터를 저장한다. 클래스에 대한 정보가 존재하기 때문에 인스턴스 생성시 해당 영역을 참조해야 하며 따라서 모든 스레드가 이 영역을 공유한다. static 변수도 해당 영역에 할당된다.

Heap Area

런타임시 동적으로 메모리가 할당되고 소멸되는 영역이며 GC(Garbage Collection)을 수행하는 영역이다. Heap 영역이 가득 차게 되면 OutOfMemoryError가 발생한다.

Heap 영역은 효율적인 GC를 위해 3가지 영역으로 구분된다.

  • Young Generation
  • Tenured Generation
  • Permanent Generation

Stack Area

메서드 호출시 지역변수, 매개변수, 함수 호출내역 등이 저장되는 영역이다. 스레드마다 개별적으로 생성되며 메서드 호출 시 생성되었다가 메서드가 종료되면 사라진다. 스레드가 사용할 수 있는 스택의 크기를 넘기면 StackOverFlowError가 발생한다. 또한 스택을 동적으로 확장할 때 확장할 메모리가 부족하거나 새로운 스레드 생성시 스택에 할당할 메모리가 부족하면 OutOfMemoryError가 발생한다.

스택에 저장되는 데이터들은 Frame이라는 자료구조로 저장되며 아래와 같은 데이터들이 저장된다.

  • Local Variables Array
  • Operand Stack
  • Frame Data

PC Register

현재 스레드가 실행되는 부분의 주소와 명령을 저장하는 영역이다. PC Register에는 멀티 스레드 프로그래밍 환경에서 한 스레드가 작업을 하다가 다른 스레드로 CPU자원을 넘겨주고 다시 받았을 때, 이어서 작업을 하기 위해 현재 실행중인 명령어의 주소를 기록한다.

Native Method Stack

자바 이외의 언어가 JVM에서 동작하기 위해 할당한 메모리 영역으로, 일반적으로 C스택을 사용한다. 스레드에서 java메소드가 아닌 Native방식을 사용하는 메소드를 실행하면 이 곳에 해당 메소드에 대한 정보를 저장한다.

3. Execution Engine

실행 엔진은 바이트코드를 읽고 실행하는 역할을 한다. 바이트 코드를 실행하는 방식에는 인터프리터, JIT 두 가지 방식이 있다.

인터프리터

인터프리터 방식이란 자바 바이트 코드를 한 줄씩 읽고 해석하는 방식을 말한다. 하나씩 해석하고 처리하기 때문에 전체 코드의 관점에서 봤을 때 속도가 느리다는 단점이 있다. 반면, 소스코드가 수정될 때마다 재컴파일해줘야 하는 정적 컴파일 방식(C, C++)과 달리 인터프리터 방식은 컴파일 과정을 거칠 필요 없이 수정이 쉽다는 장점이 있다.

JIT 컴파일러

JIT(Just-In-Time) 컴파일러는 인터프리터 방식의 단점인 속도 문제를 해결하기 위해 나온 인터프리터와 컴파일러를 결합한 방식이다. 처음엔 인터프리터 방식을 사용하다가 적정한 때에 바이트코드 전체를 기계어로 바꾼다. 인터프리터 방식으로 바이트코드를 기계어로 번역할 때 중복된 코드를 캐싱하여 똑같은 코드를 매번 번역하는 것을 방지해 속도를 보완해준다.

📝 GC 동작과정과 알고리즘에 대해 추가학습 필요

참고자료
JVM-Runtime Data Area
JVM이란?
JVM Stack & Heap

profile
나의 성장 기록

1개의 댓글

comment-user-thumbnail
2024년 10월 29일

혹시 OAuth 글은 언제 작성될까요?😊

답글 달기