[Basic-Java] 01. JVM은 무엇이며 자바 코드는 어떻게 실행하는 것인가

목표

자바 소스 파일(.java)을 JVM으로 실행하는 과정 이해하기

1. JVM이란 무엇인가

JVM이란 자바 바이트코드를 실행하기 위한 가상 컴퓨터입니다. 다시 말하면, 자바 바이트코드는 JVM위에서만 실행되기 때문에 CPU나 OS에 종속적이지 않고 실행됩니다.
(하지만 JVM자체는 OS에 종속적이기 때문에 OS별로 설치해야합니다.)

아래 이미지는 JVM 아키텍처의 다이어그램입니다.

출처: https://dzone.com/articles/jvm-architecture-explained

위 이미지에 보여지는 것처럼 JVM은 3개의 메인 서브시스템으로 구성되어 있습니다.
1. ClassLoader
2. Runtime Data Area
3. Execution Engine

ClassLoader에서는
1) Loading
2) Linking
3) Initialization

세 단계의 과정을 거칩니다.

간략하게 과정을 설명하자면
1) Loading 단계에서는 .class 클래스 파일의 위치를 찾아 그것을 JVM위에 올려놓습니다.
2) Linking 단계에서는 바이트코드 검증기가 바이트코드가 적절한지 검증한 후에 모든 static 변수에 메모리가 할당됩니다.
3) Initialization 단계에서는 모든 static 변수들이 이전단계에서 할당된 메모리영역에 실제로 값이 할당됩니다.

Runtime Data Area는 다섯개의 주요 구성요소로 이루어져 있습니다.

  1. Method 영역

    • static 변수들을 포함해, 클래스로더가 읽어온 모든 클래스레벨(클래스정보)의 데이터들이 이곳에 저장됩니다.
    • JVM당 하나의 영역만 존재하며, 자원이 공유됩니다.
  2. Heap 영역

    • 프로그램을 실행하면서 생긴 모든 Object와 해당 인스턴스 변수 및 배열이 이곳에 저장됩니다.
    • Method 영역과 마찬가지로 JVM당 하나만 존재합니다.
      참고: Method, Heap 영역은 하나만 존재하고 다중 Thread들이 해당영역의 자원을 공유하기 때문에 여기에 저장된 데이터는 thread-safe하지 않습니다.
  3. Stack 영역

    • 모든 Thread에 대해 별도의 런타임 Stack이 생성됩니다.
    • 모든 메소드 호출에 대해 Stack Frame이 생성됩니다.
      • Stack Frame은 3가지 요소를 갖습니다.
      • 1) Local Variable Array: 지역변수가 들어가 있는 어떤 저장소
      • 2) Operand stack: 연산작업을 기록하는 스택
      • 3) Frame data: 이전 스택프레임의 정보, 현재 메소드가 속한 클래스/객체에 대한 참조 등의 정보
    • 모든 지역변수가 Stack메모리에 생성됩니다.
      참고: Stack영역은 공유리소스가 아니기때문에 thread-safe합니다.

      이해를 돕기위한 그림입니다. 각 Thread별로 Stack이 생성되고 그 내부에 메소드 호출마다 Stack프레임이 생성됩니다.

  4. PC Registers

    • 각 Thread는 별도의 PC Register를 가집니다.
    • Thread는 항상 어떤 메소드를 실행하고 있는데 PC는 그 메서드 안에서 몇 번째 줄을 실행해야 하는지 나타냅니다.
  5. Native Method Stacks

    • Native Method는 C, C++ 등 운영체제에 종속적인 언어로 만든 라이브러리 등을 뜻합니다. Java언어로는 구현하기 힘든 기능들을 만들어야 할 때 Native Method를 이용해야 하는데 그것을 변환시켜주는 것이 JavaNativeInterface입니다.
    • 모든 Thread는 별도의 Native Method Stack이 생성됩니다.

Execution Engine는 Runtime Data Areas에 할당된 바이트코드를 하나씩 읽고 실행합니다.
1. 여기서 Intepriter가 바이트코드를 기계가 이해할 수 있는 기계어로 번역을 진행합니다. 하지만 여기서 반복되는 메소드를 계속해서 재번역을 진행하게 되면 성능이 저하합니다.
2. 그래서 도입한 것이 JIT(Just In Time)입니다. Java 진영에서 JIT컴파일러를 이용해 속도의 격차를 많이 줄이게 되었습니다.

2. 컴파일&실행하는 방법

컴파일이란: 사람이 작성한 소스코드를 JVM이 이해할 수 있도록 바이트코드로 변환하는 과정을 말합니다.

기본적으로 자바는 상위버전으로 컴파일한 바이트코드는 하위버전으로 실행하지 못합니다.(특별한 옵션을 준다면 하위버전으로도 실행이 가능함)

하위버전으로 컴파일한 바이트코드는 상위버전으로 실행할 수 있습니다.

class Hello {
    public static void main(String[] args) {
        System.out.println("hello hello");
    }
}

위와 같은 내용을 가진 Hello.java 파일이 있다고 가정합니다.


해당 파일을 컴파일합니다.

$ javac Hello.java


Hello.class라는 클래스파일이 만들어집니다.
해당 파일을 실행해봅니다.

$ java Hello

컴파일은 javac 명령어를 이용하고 .java확장자명을 이용하지만 실행은 .class라는 확장자명을 붙이지 않습니다.

3. 바이트코드란 무엇인가

출처: http://www.tcpschool.com/java/java_intro_programming

바이트코드란 JVM이 실행하는 명령어의 형태입니다.
그림을 보시면 컴파일러에 의해서 자바파일 -> 클래스파일로 변환됩니다.

4. JIT 컴파일러란 무엇이며 어떻게 동작하는가

Just In Time의 준말입니다.
JIT이 도입된 이유는 위 Execution Engine 부분에서 간단하게 설명드렸습니다. 여기서는 JIT이 하는 일과 왜 빠른지 알려드리겠습니다.
자바는 실제 실행되기까지 두 번의 번역을 거쳐야합니다. 그 번역은
1. 사람(소스코드) -> JVM(산출물: 바이트코드)
2. JVM(바이트코드) -> 실제 기계(산출물: 기계어)
이렇게 두 번이 됩니다.
이때 JVM에서 메소드가 실행될 때 바이트코드를 한줄한줄 정성스럽게 인터프리터 방식으로 번역하게 됩니다.(인터프리터방식)
그런데 클래스를 로딩할때 JVM이 코드를 분석하고 자주 나오는 바이트코드를 한방에 기계어로 번역 후 캐싱합니다.(메모리에)
이런식으로 JIT는 성능의 향상을 꾀합니다.

5. JDK와 JRE의 차이

출처: https://www.geeksforgeeks.org/difference-between-jdk-and-jre-in-java/

JRE: Java Runtime Environment
JDK: Java Devleopment Kit
뜻을 그대로 풀이하자면 JRE는 자바를 실행 시키는 환경입니다
JDK는 자바를 프로그래밍 하는 환경입니다.
그래서 자바를 실행시킬 수 있는 JVM은 JRE안에 포함됩니다.
다시말해, 프로그래밍을 하기 위해선 환경이 필요합니다.
따라서 JRE는 JDK의 부분집합이라고 할 수 있습니다.

좋은 웹페이지 즐겨찾기