자바 프로그램의 구동 과정과 JVM의 구조

Jay·2022년 2월 1일
0

자바 개발 환경

자바 프로그램의 구동 과정을 이해하기 위해서는 자바 개발 환경에 대한 이해가 선행되어야 한다. 크게 JDK, JRE, JVM으로 구분되고, 아래 그림과 같은 포함 구조를 가지고 있다.

  1. JDK(Java Development Kit)
    JDK는 자바 개발 도구의 축약어로 말 그대로 자바 프로그램을 만들고 실행하는데 도움을 주는 프로그램들의 집합이다. 자바 프로그램을 개발하는데 사용되는 자바 언어, 자바 컴파일러, 디버깅 툴, 모니터링 프로그램 같은 것들과 자바 프로그램을 실행해주는 JRE를 포함하는 개념이다. 위 그림에서도 JRE와 개발 도구들을 분리해서 포함하고 있는 모습을 보여준다.

  2. JRE(Java Runtime Environment)
    JRE는 자바 실행 환경이라는 의미로, JDK에 내장되어 자바 프로그램을 실행하는 역할을 한다. 자바 클래스 라이브러리, 클래스 로더 그리고 JVM으로 구성되어 있다. 자바는 운영체제에 독립적이라는 특징을 가지고 있는데(WORA: Write Once Run Everywhere) JRE가 자바와 운영체제 사이에서 조정자 역할을 하기 때문이다. JRE는 각 운영체제별로 존재해 개발자는 실행 환경을 신경쓰지 않고 코드에만 집중할 수 있고 그 코드는 어느 실행 환경에서나 작동한다.

  3. JVM(Java Virtual Machine)
    JVM은 자바 가상 기계라는 의미로 실질적으로 자바 프로그램을 실행하는 환경이다. 가상 기계라는 말 처럼 JVM이 실행되고 있는 운영체제와는 별개로 JVM내부에 별도의 실행 환경을 구축하고 있다. JVM은 javac로 컴파일된 자바 바이트코드를 실행하는 역할을 한다.

JVM의 구조

개발자가 자바 소스코드(.java)를 작성하면 JRE는 자바 컴파일러(javac)로 컴파일 해 클래스 파일(.class, 목적 파일, 바이트 코드라고도 불린다)을 생성한다. 이 클래스 파일은 JRE에 포함된 클래스 로더에 의해 JVM으로 로딩된다. JVM으로 넘어온 클래스 파일은 Runtime Data Area에 적재되어 Execution Engine에 의해 효율적으로 메모리 공간을 관리함과 동시에 실행되는 것 까지가 자바 프로그램 실행 과정의 골자다. JVM의 내부 구조를 살펴보며 위 과정을 상세하게 알아보자.

클래스 로더 (Class loader)

컴파일된 자바 바이트 코드를 자바 실행 엔진이 실행할 수 있도록 런타임 데이터 영역으로 이동시키는 역할을 한다.

런타임 데이터 영역 (Runtime Data Area)

컴퓨터에서 메모리 같은 역할을 하는 영역이다. 자바 프로그램을 실행하며 발생하는 데이터를 저장하는 역할을 하는데, 데이터의 속성에 따라 다섯 가지 영역으로 나뉘어 저장된다.

(본격적으로 시작하기 전에) 전처리 과정 🔥

JRE는 프로그램을 실행하기 전에 먼저 프로그램이 메인 메서드를 포함하고 있는지 확인하고, 메인 메서드가 존재하면 JRE는 프로그램을 JVM에 보낸다. JVM은 프로그램을 실행하기 전에 전처리 과정 이라는 것을 거치는데, 기본적인 클래스를 포함하는 java lang 패키지와 프로그램이 가지고 있는 모든 클래스와 임포트된 패키지들을 method 영역에 가져다 놓는다.

public class Calculate {                      // 클래스 : static
    static int performance = 50;              // 전역변수 : static

    public static void main(String[] args) {  // 메서드 : stack
        int exam = 45;                        // 지역변수 : stack
        int music;                            // 지역변수 : stack
        music = plus(exam, performance);

        Students jay = new Students();        // 객체 참조 변수 : stack, 인스턴스: heap

        jay.name = "Jay";                     // 인스턴스 : heap
        jay.points = music;                   // 인스턴스 : heap

        jay.postReport();                     // 인스턴스 : heap
    }

    private static int plus(int x, int y) {   // 메서드 : stack

        int result = x + y;                   // 지역변수 : stack

        return result;
    }
}

public class Students {                        // 클래스 : static
    public String name;                        // 클래스 : static 
    public int points;                         // 클래스 : static 

    public void postReport() {                 // 클래스 : static
        System.out.println(name + "의 음악 점수는 " + points + "점 입니다.");
    }
}

Method(static) Area
스태틱(Static) 영역이라고도 불리는 메서드 영역이다. JVM이 실행되면 전처리 과정을 거치며 프로그램이 실행 되면서 호출되는 클래스 및 클래스 내부에 있는 전역 변수, 정적 멤버와 임포트된 패키지들 그리고 임포트 되지 않아도 기본적으로 프로그램에 포함되는 java lang 패키지가 메서드 영역에 올라가게 된다. (처음 프로그램이 실행될 때 모든 프로그램의 주소 공간을 메모리에 로딩하는 것이 아닌, 실행 중에 필요한 데이터를 메모리에 로딩하는 방식을 동적로딩 이라고 한다)
Static 이라는 단어는 사전적으로 '(변화 / 움직임 없이) 고정된, 정적인'이라는 의미를 가지고 있다는 점을 기억하면 스태틱 영역이 왜 패키지들과 클래스들을 가지고 있는지 쉽게 이해할 수 있다.

Stack
스택 영역은 실행되는 메서드 및 중괄호 블록(ex. if문)에 대한 데이터가 저장되는 메모리 영역이다. 그래서 맨 처음 실행되는 메서드인 메인 메서드가 가장 첫번째로 메모리에 올라가고, 그 다음에 실행되는 메서드들이 메인 메서드 위에 쌓이는 식이다.(stack 자료구조의 특징) 그래서 메인 메서드가 스택 영역에서 작업을 마치고 제거되면 프로그램도 종료된다.

스택 영역에 쌓이는 메서드 단위를 메서드 스택 프레임이라고 부르는데, 메서드 스택 프레임에는 메서드 실행을 위한 매개변수, 지역변수, 혹은 메서드 내부에 메서드나 블록이 존재하면 스택 프레임 내에 스택 프레임이 생길 수도 있다.

위의 예에서 메인 메서드가 plus 메서드를 호출할 때 스택 영역에 있는 메인 메서드 스택 프레임 위에 플러스 메서드 스택 프레임이 생긴다. 플러스 메서드의 작업이 종료되면 스택에서 제거되고 다시 메인 메서드 스택 프레임만 남게된다.

스택 영역의 가장 큰 특징은 멀티 스레드 환경에서 각 스레드가 고유의 스택 영역을 가진다는 점이다. (멀티 프로세스 환경에서는 각 프로세스가 별개의 메모리 환경을 가지고 있다) 그래서 스택 영역은 여러개로 분할 될 수 있다.

Heap
Heap은 static 영역과는 반대로 동적으로 생성된 객체가 저장되는 영역이다. 위 예시의 메인 메서드 내부에서 students 클래스의 인스턴스(jay)를 생성하면 Students의 인스턴스가 Heap 영역에 생성되고, 메인 메서드 내부에 있는 객체 참조 변수 jay가 heap에 있는 students 인스턴스를 가리킨다. 즉, 인스턴스의 메모리 주소를 담게 된다.

PC Register
스택 영역과 마찬가지로 스레드 별로 각자 할당된 PC 레지스터 영역을 가지고 있다. PC 레지스터는 운영체제에서 사용되는 PC와 동일한 의미로 현재 수행중인 스레드의 명령어 주소를 저장한다.

Native Method Stack
JAVA가 아닌 다른 언어로 작성된 코드를 위한 공간이다. 즉 c/c++/기계어 등의 코드를 실행하기 위한 공간이다.

실행 엔진 (Execution Engine)

Runtime Data Area에 저장된 바이트 코드를 읽어 명령을 실행한다.

Interpreter
런타임 시에 바이트 코드를 한 줄씩 읽어가며 기계어로 번역한다. 단점은 한 메소드가 여러번 실행될 때마다 매번 해석을 반복해야 하는 비효율적인 면이 있다.

JIT(Just-In-Time) Compiler
Interpreter의 단점을 완화해주는 컴파일러다. 실행시에 인터프리터 방식으로 바이트 코드를 기계어로 한 줄씩 번역한 후 캐싱과정을 거치기 때문에 반복된 요청이 있을 경우 번역을 번복하지 않아도 돼 작업 속도를 단축시켜준다. 하지만 캐시는 비싼 자원이기 때문에 기계어를 저장하는 것은 낭비고 모든 코드를 반복해서 사용하는 것은 아니기 때문에 한 번 실행할 코드는 인터프리터를 사용하는 것이 낫다.

쓰레기 수집 (GC, Garbage Collection)
GC는 동적 할당된 메모리 영역(heap)에서 더이상 참조되지 않는 데이터를 탐색후 제거해 메모리를 확보하는 역할을 한다. 자바에서 gc는 자동적으로 실행돼 개발자가 메모리를 관리하지 않고 코드에만 집중하게 해준다는 장점이 있다.

profile
You're not a computer, you're a tiny stone in a beautiful mosaic

0개의 댓글