JVM( Java Virtual Machine )이란

업데이트:
7 분 소요

제임스 고슬링(James Gosling)이 개발한 JAVA 언어를 깊이 이해하기 위해서는 JVM( Java Virtual Machine )의 구조 파악(너무 깊지 않게)이 선행적으로 필요하다고 생각되어 이전 공부했던 내용을 다시 정리하겠습니다.

Step 1: JVM의 기본 구성

기본적으로 JAVA 프로그램이 사용자에 의해 실행되면 JVM은 운영체제로부터 필요한 메모리를 할당받아 JAVA 프로그램을 처리합니다. 1995년부터 1999년도 윈도우 프로그램 개발이 대세였던 시절 JAVA는 메모리 및 CPU를 지나치게 많이 사용한다는 단점 때문에 많이 사용되는 언어가 아니었습니다. C++언어가 주류를 이루었지만 1999년도부터 인터넷이 활성화되고 하드웨어의 큰 발전과 스마트폰의 등장은 JAVA 붐을 일으켰고 이제는 세계시장에서 점유율 1위라는 타이틀을 차지하고 있습니다. 이전에는 윈도우환경, 리눅스 또는 유닉스 환경에서 실행되는 프로그램 만들기 위해서는 각자의 환경에 따라 개발을 달리 해야 하는 문제가 있었습니다. 하지만 JAVA는 JVM을 통하여 실행되기 때문에 어떠한 운영체제에서도 동일한 형태로 실행시킬 수 있다는 장점이 있습니다.
2019 java 점유율
JVM은 크게 Garbage collector, Execution Engine, Class Loader, Runtime Data Area 4가지 영역으로 나누어집니다. 이 중에서 개괄적으로 Garbage collector, Execution Engine, Class Loader에 대해 설명하고 메모리 구조를 파악하기 위해 Runtime Data Area에 대해 조금 더 정리하겠습니다. Garbage collector, Execution Engine, Class Loader는 차츰차츰 단독 포스트로 하여 기존 공부한 부분과 새롭게 알게 된 부분을 다시 정리하겠습니다.

좀 더 정확한 개념을 위한 설명

JVM
자바 바이트코드들을 OS에서 어떻게 실행할 지에 대한 표준 스펙이며 다양한 구현체가 존재합니다. JAVA라는 언어로 개발하기 때문에 OS에 독립적이란 의미지 JVM자체로는 OS마다 어떻게 실행되어야 하는지 정의된 것을 구현하기 때문에 OS에 종속적입니다.
좀 더 정확히는 JVM은 하나의 표준이자 스펙입니다. 이러한 JVM 표준을 가지고 특정 밴더( 오라클, 아마존 등등 )들이 JVM을 구현합니다.

JRE
JAVA어플리케이션을 실행하기 위한 최소 배포 단위로서 JVM과 필요 Library가 포함됩니다.( 실행이 목적이라 개발 관련 도구는 포함되지 않습니다.)
오라클은 자바 11부터 JDK만 제공하며 JRE를 따로 제공하지 않습니다.

JDK
JRE + 개발에 필요한 툴이 포함됩니다.
오라클은 자바 11부터 JDK만 제공하며 JRE를 따로 제공하지 않습니다.
소스코드를 작성할 때 사용하는 자바는 플랫폼 독립적입니다.

JAVA 유료화에 대한 잘못된 상식
JAVA언어가 유료화가 되는 것이 아니라 오라클에서 만든 JDK 11버전부터 상용으로 사용할 때 유료입니다. 즉 여러 JDK 중에서 오라클에서 만든 Oracle JDK11만이 유료입니다. 다른 Oracle open JDK는 무료이며 아마존에서 만든 JDK, 다른 벤더들이 구현한 JDK는 무료입니다.

① Garbage collector(GC)

잠시 후에 공부할 Runtime Data Area 중 Heap영역에 더 이상 사용하지 않고 자리만 차지하고 있는 객체들을 제거하는 역할을 합니다. 자동으로 실행되기 때문에 언제 정확히 언제 실행되는지 알기 어렵고 사용자가 임의로 GC를 발생시키는 것은 좋은 발상이 아니기에 JVM에 맡기는 것이 좋습니다. 기본적인 특징은 GC가 수행되는 동안 GC를 실행하는 쓰레드 외 모든 쓰레드가 일시정지 됩니다.

② Class Loader

개발의 결과물로 우리는 .java파일을 생성합니다. 이러한 .java파일은 컴파일러(javac)에 의해 .class파일 즉 JAVA Byte Code(바이트코드) 로 컴파일되고, 이렇게 컴파일된 바이트코드들을 Class Loader가 운영체제에 의해 메모리를 할당 받은 Runtime Data Area으로 적재하는 역할을 합니다.

③ Execution Engine

Class Loader에 의해 Runtime Data Area에 적제된 .class파일들을 하나의 명령단위로 읽어서 컴퓨터가 이해할 수 있는 기계어로 번역하고 명령을 실행합니다. Interperter방식과 JIT(Just-In-Time) 방식이 존재하고 추후 포스트에 자세히 다루어 보겠습니다.

④ Runtime Data Area

JVM메모리 영역으로 OS로부터 별도로 메모리 공간을 할당받고 JAVA 어플리케이션을 실행할 때 사용됩니다. Method Area, Heap Area, Stack Area, PC register, Native Method Stack 총 5가지로 구분됩니다.

jvm의 구조

Step 2: Runtime Data Area

① Method Area(Static Area)

Method Area(Static Area) 는 java 어플리케이션이 실행되면 클래스 별로 클래스에서 필요한 패키지 클래스, 런타임 상수풀, 인터페이스, 상수, static변수, final 변수, 클래스 멤버 변수 등 필드데이터, 생성자를 포함한 모든 메서드 정보 등 한번 로드 된 후 메모리에 항상 상주하고 있는 영역입니다. 그래서 모든 스레드가 공유가능 합니다. 예를 들어 Math.abs(-10) 같이 Math 클래스의 메서드를 초기화 없이 바로 사용할 수 있는것은 해당 영역에 덕분입니다.

② Heap Area

Heap Area은 메서드 안에서 사용되는 객체들을 위한 영역으로 new를 통해 생성된 객체, 배열, immutal 객체 등의 값이 저장됩니다. 해당 영역에서 생성된 객체들은 다음에 나올 JVM Stack Area의 변수나 다른 객체의 필드에서 참조 가능합니다. 때문에 해당 객체를 참조하지 않으면 더는 쓸모가 없어 Garbage Collector에 의해 객체가 Heap 영역에서 제거됩니다.

③ Stack Area

Stack Area은 스레드마다 하나씩 존재하며 쓰레드가 시작될 때 할당됩니다. 내부에는 매소드에서 직접 사용할 지역 변수, 파라미터, 리턴 값, 참조 변수일 경우 주소 값 들이 저장됩니다. 만약 프로그램에서 매소드가 호출되면 매소드와 매소드 정보는 차곡차곡 Stack에 쌓이면서(PUSH) 내부 과정을 실행합니다. 매소드 호출이 종료되면 해당 매소드는 Stack Area에서 제거(POP) 됩니다. jvm stack 영역

④ PC register

PC register은 Thread가 생성될 때마다 생성되는 영역으로 Program counter 즉 현재 쓰레드가 실행되는 부분의 주소와 해당 명령을 저장하고 있는 영역입니다. 이것을 이용해 다수의 쓰레드들이 명령의 흐름을 잃지 않고 함수가 순차적으로 실행됩니다.

⑤ Native Method Stack

Native Method Stack은 자바 외 다른 언어로 작성된 네이티브 코드를 수행하기 위한 메모리 영역입니다. JAVA 이외의 다른 언어에서 제공되는 Method의 정보가 저장됩니다.

이제는 Runtime Data Area영역 중 Heap Area영역이 어떻게 구성되어 있는지 살펴보겠습니다.

Step 3: Heap Area

jvm Heap 영역 힙 영역은 Eden, Survivor1, Survivor2, Old 영역, Permanent영역( 생성된 객체들의 주소값이 저장되는 영역 )으로 나누어집니다. 힙을 이렇게 나눈 이유는 효율적인 GC를 위해서입니다. GC는 크게 Minor GC와 Major GC로 구분되는데 이를 살펴보겠습니다. 우선 new를 통해 객체가 생성되면 Eden영역에 생성됩니다. new를 통해 다양한 객체가 생성 중 만약 Eden 영역이 가득 차게 된다면 첫 GC가 일어납니다. Eden에서 참조되지 않은 객체를 제거하고 참조가 유지되는 객체를 S1 영역으로 이동시킵니다. 다시 new를 통한 객체가 생성중 또 Eden 영역이 가득 차게 되면 이번에는 S1 영역이 아닌 S2 영역으로 참조를 유지하고 있는 객체들을 이동시킵니다. 이와 더불어 S1 영역에 있는 객체 중 참조가 유지되고 있는 객체 또한 S2로 이동시키고 age를 +1 합니다. 이를 Minor GC라고 부릅니다. 이렇게 Eden -> S1 -> S2->S1…이 반복적으로 일어나게 되면서 오랫동안 살아남는 객체는 age가 지속해서 증가하게 되는데 어느 정도 age가 증가하여 오랫동안 살아남으면 이제는 Old 영역으로 객체를 옮기게 됩니다. Major GC는 Old 영역에 있는 모든 객체를 검사하고 참조되고 있는지 확인 후 참조되지 않은 객체들을 모아 한번에 제거하는 것을 말합니다. 이를 Full GC라고도 하며 Full GC는 GC를 제외한 모든 쓰레드가 중지되어야 합니다.

Step 4: 예제로 알아보기

지금까지 JVM의 메모리 구조에 대해 알아봤습니다. 그렇다면 좀 더 명확히 이해하기 위해 예제를 통해 Class 코드들이 어디에 위치하고 어떻게 동작하는지 알아보겠습니다.

// TEST 클래스
public class Test {
    //클래스 필드
    public static int ONE       = 1;
    public static String TWO    = "TWO";
    public static String FOUR;
    
    // 상수
    public static final int FIVE    = 10;

    // 정적 블럭
    static {
        FOUR = "안녕하세요";
    }
    
    //인스턴스 필드
    private int three;
    
    public Test( int three ) {
        this.three = three;
    }
    
    // 클래스 메서드
    public static int sum( int a , int b ) {
        return a + b;
    }
    
    //인스턴스 메서드
    public int multiply( int a, int b ) {
        return a*b;
    }

    public int getThree() {
        return three;
    }
}

// main thread
 public static void main(String args[]){
        int one       = Test.ONE;
        String two    = Test.TWO;
        int one_sum_two = Test.sum(1, 2);
        
        Test test = new Test(3);
        int three = test.getThree();
        int two_multiply_three = test.multiply(2, 3);
        
        System.out.println("one : " +one);
        System.out.println("two : " +two);
        System.out.println("one_sum_two : " +one_sum_two);
        System.out.println("three : " +three);
        System.out.println("two_multiply_three : " +two_multiply_three);
        System.out.println("FOUR : " +Test.FOUR);
        System.out.println("FIVE : " +Test.FIVE);
 }
1
2
3
4
5
6
7
8
// 출력 
one : 1
two : TWO
one_sum_two : 3
three : 3
two_multiply_three : 6
FOUR : 안녕하세요
FIVE : 10

Class Loader에 의한 Test.class파일의 로드 과정

jvm structure 영역
① Main 메서드 안에서 int one = Test.ONE;을 만났다!

첫째로 컴파일 시점이 아닌 runtime 시점에서 모든 과정은 시작됩니다. 직접 만든 java 어플리케이션을 실행하는 도중(main 메서드 실행 중) 위 Test.class파일을 사용하는 부분을 처음 만나는 순간 Class Loader에 의해 Test.class 파일이 Loading, Linking, Initialization 과정을 겪습니다.

Loading

BootStrap ClassLoader, Extension ClassLoader, Application ClassLoader 3개의 클래스 로더를 통해 Test.class파일이 JVM안으로 로드됩니다. Class Loading을 요청받았을 때 BootStrap ClassLoader > Extension ClassLoader > Application ClassLoader 순서로 우선권을 가지며 BootStrap ClassLoader에 없다면 Extension ClassLoader을 찾고 Extension ClassLoader에 없다면 최종적으로 Application ClassLoader에서 찾는다고 생각하시면 됩니다. Test.class파일은 Application ClassLoader가 찾아 Loading합니다.

  • BootStrap : 가장 최상위 ClassLoader로 java.lang.Object같은 핵심 시스템 Class를 로딩합니다. $JAVA_HOME/jre/lib 위치에 있는 JAR 파일이 그 대상입니다. 위를 변경하기 위해서는 -Xbootclasspath옵션을 통해 변경 가능합니다.

  • Extension ClassLoader : $JAVA_HOME/jre/lib/ext에 위치한 JAR 파일을 로딩합니다. 사용자의 classpath를 수정하지 않고 다양한 보안 확장프로그램 같은 새로운 확장 프로그램을 로드할 수 있습니다.

  • Application ClassLoader : $CLASSPATH에 위치한 JAR 파일을 로딩합니다. 직접 만든 어플리케이션의 JAR파일의 classpath라고 보시면 됩니다.

Linking

  • Verify : 로드된 Test.class파일들의 에러를 확인합니다.
  • Prepare : Test.class파일들을 훝으면서 static키워드가 붙은 멤버들에 대해 메모리만 할당하고 기본값으로 세팅합니다. (아직 정확하게 값을 넣기 전입니다.) 즉 public static int ONE = 0;으로 세팅합니다.
  • Resolve : Test.class파일들을 훝으면서 지금은 없지만 symbolic reference( Test.TWO와 같이 객체 이름으로 참조하는 것)들을 Method Area에서 실제 주소값을 찾고 그 주소값으로 참조객체들을 세팅합니다.

Initialization

최종 단계로 모든 정적 변수는 원래 값으로 할당되고 정적 블록이 실행됩니다. public static int ONE = 1값으로 할당하고, static 블록 내부 FOUR=”안녕하세요” 를 실행합니다. 이때 Method Area로 Test.class에 있는 모든 정보들이 Method Area안 독립적인 Test.class 영역으로 배치됩니다.

Method Area structure 영역

Field Information

Class 멤버변수의 이름 및 데이터 타입, 접근 제어자에 대한 정보를 저장

Method Information

Class 멤버메서드의 이름, 리턴타입, 매개변수, 접근제어자에 대한 정보

Type Infomation

Type의 속성이 Class인지 Interface인지여부 , 패키지명, Supper Class에 대한 정보

Constant Pool

class에서 사용된 상수가 저장되는 영역, Symbolic reference 객체도 여기에 저장된다.

Static Variable

static 변수들에 대한 정보들이 저장된다.

② Main 메서드 안에서 Test test = new Test(3);을 만났다!

Method Area에서 객체정보를 읽어 Heap메모리 안에 독립적으로 생성합니다.

Heap Area structure 영역

Info: 지금까지 이전에 공부했던 내용을 정리해보면서 내용을 다듬었습니다. 잘못된 부분은 댓글 남겨주시면 수정하겠습니다. 감사합니다.

참고자료
https://dzone.com/articles/jvm-architecture-explained

카테고리:

업데이트:

댓글남기기