JVM에 대해 알아보자!

MinKeun Kim·2021년 6월 15일
0

JAVA

목록 보기
2/3
post-thumbnail

지난 포스팅 중에 자바 컴파일 과정에 대해 알아보았습니다.

자바 컴파일러에 의해 변환된 .class파일 (바이트 코드)이 클래스 로더에 의해

JVM 내에 들어와 메모리 공간을 만들어주고 컴퓨터가 이해할 수 있는 기계어로

만들어준다고 했습니다.

결국엔, JVM 안에서 메모리도 만들고, 기계어로 변환도 해주고

여러 역할을 수행하게 됩니다.


그래서 JVM이 무엇이냐?

Java Virtual Machine의 약자로서,

자바 바이트코드를 실행할 수 있는 주체입니다.

일반적으로 인터프리터나 JIT 컴파일 방식으로 다른 컴퓨터 위에서 바이트코드를

실행할 수 있도록 구현됩니다.

출처 : 위키백과


쉽게 말해서, JVM은 우리들이 작성한 자바 프로그램이 수행되는 프로세스를 의미합니다.

다시 말해서, java라는 명령어를 통해서 애플리케이션이 수행되면,

이 JVM 위에서 애플리케이션이 동작합니다.

이 JVM에서 우리들이 작성한 프로그램을 찾고 실행하는 일련의 작업이 진행됩니다.


이 글을 통해, JVM을 처음 접한 분들은

'아, JVM이 컴파일 과정 뒤에 프로그램이 실행되기까지의 작업들을 해주는 곳이구나!' 라고 이해하셨을텐데요,

반면에, JVM의 개념을 알고 있던 분들은 여기서 끝나기엔 무언가 부족할 것입니다.


자, 그럼 JVM의 아키텍쳐를 이해해봅시다.


JVM은 세 가지 구성요소가 있습니다.

  1. Class Loader
  2. Runtime Data Area (메모리 영역)
  3. Execution Engine

그림을 보면서 해당 구성요소들을 확인해봅시다.


첫 번째, Class Loader

가장 먼저, 클래스 로더는 총 3가지의 단계를 가지고 있습니다.

  1. Loading
  2. Linking
  3. Initialization

위에서 잠깐 언급했듯이, 클래스 로더는 컴파일된 바이트 코드 파일을

JVM 내로 옮기는 역할을 합니다.

그런 역할을 하는 클래스 로더는 총 3가지 단계를 거칩니다.


1. Loading
: 총 3가지의 loader 시스템

1. Bootstrap class loader

해당 로더는 Runtime.jar의 클래스 파일을 옮기는 역할을 합니다.

예를 들어, System.out.println(""); 과 같은 코드가 있을 때,

System 클래스를 사용하기 위해서 java.lang 패키지가 필요합니다.

물론, 우리가 직접 넣어줄 필요는 없는 패키지이지만,

Bootstrap class loader가 해당 패키지가 들어 있는 Runtime.jar를 메모리로

옮겨주는 역할을 합니다.


2. Extention class loader

우리는 오라클 데이터베이스에 연결하려면 JDBC.jar가 필요한데요,

Extention class loader는 이러한 타사 jar파일을 옮기는 역할을 합니다.


3. Application class loader

이름 그대로 애플리케이션 파일을 즉, 컴파일이 된 응용 프로그램을

메모리에 옮기는 역할을 합니다.



2. Linking
이제 연결 단계를 수행해야 합니다.
클래스 로더의 Linking에는 Verify(확인), Prepare(준비), Resolve(해결)를 수행합니다.

1. Verify

클래스 파일이 메모리에 적재되면, 해당 클래스 파일들을 잘 있는지 확인합니다.

혹시라도 바이트 코드를 수정했을 수 있기 때문에, .class 파일 형식이

유효한지 체크합니다.

2. Prepare

준비 단계에서는 정적 변수에 대해 메모리가 할당되고 기본값이 할당됩니다.

예를 들어, static int count = 0; 이런 변수들이 할당됩니다.

3. Resolve

심볼릭 메모리 레퍼런스를 메소드 영역에 있는 실제 레퍼런스로 교체합니다.

public class GidhubApplication {
    HelloGid helloGid = new HelloGid();

    public static void main(String[] args) {
        SpringApplication.run(GidhubApplication.class, args);
    }
} 

예를 들어, HelloGid helloGid = new HelloGid()에서 new HelloGid() 부분은

실제 레퍼런스를 가리키진 않습니다.

그렇기 때문에, 실제 힙에 들어있는 인스턴스를 가리키는 작업을

Resolve 시점에 해주게 됩니다.


3. Initialization

위 Prepare 단계에서 확보한 메모리 영역에 모든 static 값(정적 변수)들을 할당합니다.




두 번째, RunTime Memory Data (메모리 영역)

두 번째로, 메모리 영역에 관해 말씀드리겠습니다.

결국 이 개념은 자동차가 도로를 필요로 하는 것과 같습니다.

기차가 달리기 위해선 기찻길이 필요하듯이, JVM은 이제 저장할 메모리 영역이 필요합니다.

5가지의 메모리 데이터 영역이 있습니다.

1. Method Area

모든 클래스 레벨의 데이터들이 저장됩니다.

Class Employee {
	static int count = 0;
    ...
}

2. Heap area

객체와 인스턴스 변수가 저장됩니다.

...new Employee();

3. Stack Memory

모든 지역 변수를 저장합니다.

또한, 쓰레드마다 런타임 Stack을 생성합니다.

쓰레드가 종료되면 Stackdms 초기화 되고,

해당 쓰레드안에서 이뤄지는 작업들은 Stack Frame Block 단위로 Stack에 적재됩니다.

4. PC Register

쓰레드 내 현재 실행할 Stack Frame을 가리키는 포인터 입니다.

실은, Stack영역에는 막대한 양의 Stack Frame이 적재되어 있습니다.

그런데 외부 요인으로 인해 현재 쓰레드가 점유하여 사용하고 있는 CPU 자원이 방해 받을 수 있습니다.

이 경우, 현재까지 작업하고 있는 내용은 Stack에 저장하고, 나중에 다시 쓰레드가 CPU를 할당 받았을 경우

어떤 작업부터 다시 수행해야 하는지 가리키는 변수가 바로 PC 레지스터 입니다.

5. Native Method Stack

C, C++로 구현되어있는 라이브러리를 사용하는 경우,
JNI(Java Native Interface)를 통해 Native Method Library를 사용하도록 하는 영역입니다.

JNI이란,
Java 어플리케이션에서 C, C++ 등 언어로 작성된 함수를 사용할 수 있는 인터페이스입니다.



세 번째, JVM Execution Engine

마지막으로,

바이트 코드들을 컴퓨터가 이해할 수 있는 언어(기계어)로 바꾸어주는 실행 엔진과

자바의 메모리 구조를 자동으로 관리해주는 GC(Garbage Collection)가 있습니다.

1. Interpreter

인터프리터는 Byte Code를 기계가 이해할 수 있도록 Native Code로 바꾸는 작업을 합니다.

그리고 그 동작 과정은 Byte Code 한 줄마다 컴파일을 하여 Native로 변환하는 작업을 하게 되는데,

중복되는 Byte Code들에 대해서도 매번 컴파일을 하게 되어 비효율적이며,

Running Time도 길어지게 됩니다.


이러한 단점을 보완하기 위해 나온 것이 JIT(Just-In-Time) Compiler 입니다.


2. JIT Compiler

JIT Compiler는 인터프리터 방식으로 실행을 하다가,

반복되는 코드를 발견하면, 적절한 시점에 바이트 코드 전체를 컴파일 하고

더 이상 인터프리팅 하지 않고 해당 코드를 직접 실행합니다.

JIT Compiler에 의해 해석된 코드는 캐시에 보관하기 때문에,

한 번 컴파일 된 후에는 빠르게 수행하는 장점 있습니다.

반면, 처음 시작할 때에는 변환 단계를 거쳐야 하므로 성능이 느리다는 단점이 존재합니다.

하지만, CPU 성능이 점점 좋아지고 JDK의 성능 개선도 많이 이루어졌기 때문에

이러한 단점도 많이 개선되었습니다.


3. GC (Garabage Collection)

더 이상 참조되지 않는 객체들을 정리합니다.

사실, GC에 대한 내부 구조와 원리를 이해하는 것도 중요합니다.

하지만, 이것 또한 포스팅 하나 분량이므로 추후에 상세히 다뤄보도록 하겠습니다. :)




마무리

지금까지 JVM에 대한 개념과 아키텍쳐를 알아보았습니다.

실제로, 하나의 자바 파일이 컴파일만 되고 끝나는 것이 아니라,

그 바이트 코드 파일을 가지고 Java Application 내에서 어떻게 메모리로

옮겨 가고, 어떤 절차를 거치는지 상세히 알아보았습니다.

밑에 링크1에 담긴 유투브 강의를 한번 더 들으시면서,

머릿속에 JVM을 잘 정리하시길 바라겠습니다.



출처
링크1
링크2

profile
자바 백엔드 개발자 입니다

0개의 댓글