Skip to content

김준기, 이영민, 박정제, 박민지 JVM

lass9436 edited this page Jul 10, 2024 · 3 revisions

코드가 실행 되기까지의 과정

자바 코드를 작성하고 이 코드가 JVM을 통해 실행되기까지의 과정을 단계별로 자세히 설명해드리겠습니다. 첫 단계부터 자세히 설명하겠습니다.

1. 자바 코드 작성

1.1. 텍스트 편집기/IDE 선택

자바 코드를 작성하려면 텍스트 편집기나 통합 개발 환경(IDE)을 사용해야 합니다. 일반적으로 많이 사용하는 IDE는 IntelliJ IDEA, Eclipse, NetBeans 등이 있으며, 텍스트 편집기로는 VSCode, Sublime Text, Atom 등이 있습니다.

1.2. 자바 프로젝트 생성

IDE를 사용하면 새로운 자바 프로젝트를 쉽게 생성할 수 있습니다. 예를 들어 IntelliJ IDEA를 사용하는 경우:

  1. File 메뉴에서 New -> Project를 선택합니다.
  2. 프로젝트 종류로 Java를 선택하고 Next를 클릭합니다.
  3. 프로젝트 이름과 저장 위치를 설정하고 Finish를 클릭합니다.

1.3. 자바 클래스 작성

새로운 자바 클래스를 작성합니다. 클래스 이름은 관례적으로 대문자로 시작하며, 파일 이름과 동일해야 합니다. 예를 들어, HelloWorld.java 파일에 다음과 같은 클래스를 작성할 수 있습니다.

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

여기서:

  • public class HelloWorldHelloWorld라는 이름의 공용 클래스를 정의합니다.
  • public static void main(String[] args)는 프로그램의 진입점인 main 메서드를 정의합니다.
  • System.out.println("Hello, World!");는 콘솔에 "Hello, World!"를 출력하는 명령입니다.

2. 소스 코드 컴파일

2.1. 컴파일러 선택

자바 소스 코드를 컴파일하기 위해 JDK(Java Development Kit)가 설치되어 있어야 합니다. JDK에는 javac라는 자바 컴파일러가 포함되어 있습니다.

2.2. 컴파일 명령 실행

작성한 자바 소스 파일을 컴파일합니다. 터미널이나 명령 프롬프트를 열고, 소스 파일이 있는 디렉터리로 이동한 후 다음 명령을 실행합니다.

javac HelloWorld.java

이 명령을 실행하면 HelloWorld.java 파일이 바이트코드로 변환되어 HelloWorld.class 파일이 생성됩니다.

3. 클래스 로딩

3.1. JVM 시작

컴파일된 바이트코드 파일을 실행하려면 JVM을 시작해야 합니다. java 명령을 사용하여 JVM을 시작하고 클래스 파일을 로드합니다.

java HelloWorld

JVM은 HelloWorld.class 파일을 찾아 메모리에 로드합니다.

3.2. 클래스 로더 시스템

JVM의 클래스 로더 시스템은 여러 단계로 이루어져 있습니다.

  • Bootstrap ClassLoader: JVM의 핵심 클래스들을 로드합니다.
  • Extension ClassLoader: 확장 라이브러리를 로드합니다.
  • Application ClassLoader: 사용자가 작성한 애플리케이션 클래스를 로드합니다.

4. 바이트코드 검증

4.1. 검증 단계

JVM은 로드된 바이트코드가 올바른지 검증합니다. 이 과정에서는 바이트코드가 JVM 규칙을 준수하는지 확인합니다. 검증 단계는 다음과 같이 이루어집니다:

  • 파일 형식 검증: 클래스 파일 형식이 올바른지 확인합니다.
  • 바이트코드 검증: JVM 명령어가 올바르게 구성되었는지 확인합니다.
  • 링크 검증: 클래스, 필드, 메서드의 유효성을 확인합니다.

5. 실행 엔진

5.1. 인터프리터

인터프리터는 바이트코드를 한 줄씩 해석하고 실행합니다. 초기 실행 속도는 느리지만, 실행의 유연성을 제공합니다.

5.2. JIT 컴파일러

JIT(Just-In-Time) 컴파일러는 자주 실행되는 바이트코드 부분을 기계어 코드로 변환하여 실행 속도를 향상시킵니다. JIT 컴파일러는 인터프리터와 협력하여 성능을 최적화합니다.

5.3. 가비지 컬렉터

가비지 컬렉터는 사용되지 않는 객체를 자동으로 메모리에서 제거하여 메모리 누수를 방지합니다. 이는 JVM의 메모리 관리를 최적화하는 중요한 기능입니다.

6. 네이티브 메서드 인터페이스 (JNI)

6.1. 네이티브 메서드 호출

자바 프로그램이 네이티브 메서드를 호출해야 할 때, JNI를 사용합니다. JNI는 자바 코드와 네이티브 코드를 연결하는 인터페이스로, C/C++로 작성된 라이브러리를 사용할 수 있게 합니다.

7. 프로그램 실행

7.1. main 메서드 호출

JVM은 모든 준비가 완료되면 main 메서드를 호출하여 프로그램을 실행합니다. 예제에서는 HelloWorld 클래스의 main 메서드를 실행하고, 콘솔에 "Hello, World!"를 출력합니다.

Hello, World!

이와 같이, 자바 프로그램이 실행되기까지의 전체 과정을 단계별로 자세히 설명드렸습니다. 각 단계는 자바 프로그램이 안전하고 효율적으로 실행될 수 있도록 하는 중요한 과정입니다.

image

1. 자바 코드 작성

새로운 자바 클래스를 작성합니다. 클래스 이름은 관례적으로 대문자로 시작하며, 파일 이름과 동일해야 합니다. 예를 들어, HelloWorld.java 파일에 다음과 같은 클래스를 작성할 수 있습니다.

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

2. 소스 코드 컴파일

자바 컴파일러(javac)가 자바 소스 파일을 컴파일하는 과정은 다음과 같이 요약할 수 있습니다:

  1. 소스 코드 읽기: .java 파일을 읽어들여 문자열로 저장.
  2. 구문 분석 (Parsing):
    • 어휘 분석 (Lexical Analysis): 소스 코드를 토큰으로 분리.
    • 구문 분석 (Syntax Analysis): 토큰을 이용해 구문 트리 생성.
  3. 의미 분석 (Semantic Analysis): 변수 선언, 타입 검사, 메서드 호출 등이 올바른지 확인.
  4. 바이트코드 생성 (Bytecode Generation): 구문 트리를 자바 바이트코드로 변환.
  5. 클래스 파일 생성 (Class File Generation): 바이트코드를 .class 파일에 저장.

5. 바이트코드 생성 (Bytecode Generation)

최적화된 중간 코드는 최종적으로 자바 바이트코드로 변환됩니다. 바이트코드는 JVM이 이해할 수 있는 명령어 집합으로, .class 파일에 저장됩니다. 바이트코드는 플랫폼 독립적이며, JVM이 실행할 수 있는 형태로 변환된 것입니다.

예를 들어, HelloWorld 클래스의 main 메서드는 다음과 같은 바이트코드로 변환될 수 있습니다:

public static void main(java.lang.String[]);
  Code:
     0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
     3: ldc           #3                  // String Hello, World!
     5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
     8: return

  • 바이트 코드 분석

    만들어진 자바 바이트코드는 JVM(Java Virtual Machine)이 실행할 수 있는 명령어 집합으로 변환된 코드입니다. 자바 바이트코드는 플랫폼 독립적이며, JVM이 설치된 어떤 플랫폼에서도 실행될 수 있습니다. 바이트코드는 각종 명령어와 이를 위한 데이터로 구성된 이진 형식의 파일로, .class 파일에 저장됩니다.

    바이트코드는 JVM 명령어 세트를 사용하며, 각 명령어는 특정 작업을 수행합니다. 아래에서 자바 바이트코드의 구조와 주요 명령어에 대해 설명드리겠습니다.

    바이트코드 구조

    자바 바이트코드 파일(클래스 파일)은 여러 섹션으로 구성됩니다. 주요 섹션은 다음과 같습니다:

    1. 마법 숫자 (Magic Number): 클래스 파일의 시작을 나타내는 고유 값으로, 항상 0xCAFEBABE입니다.
    2. 버전 정보 (Version Information): 클래스 파일의 버전 정보를 포함합니다.
    3. 상수 풀 (Constant Pool): 클래스 파일에서 사용되는 모든 상수(문자열, 숫자, 클래스 참조 등)를 저장합니다.
    4. 접근 제한자 (Access Flags): 클래스의 접근 제한자(public, private 등)와 관련된 정보를 저장합니다.
    5. 이름 (Class Name): 클래스의 이름과 슈퍼클래스의 이름을 저장합니다.
    6. 인터페이스 (Interfaces): 클래스가 구현하는 모든 인터페이스를 나열합니다.
    7. 필드 (Fields): 클래스의 필드(변수) 정보를 저장합니다.
    8. 메서드 (Methods): 클래스의 메서드 정보를 저장합니다.
    9. 속성 (Attributes): 클래스, 필드, 메서드와 관련된 추가 속성을 저장합니다.

    주요 바이트코드 명령어

    자바 바이트코드는 다양한 명령어로 구성되어 있으며, 각 명령어는 고유한 1바이트 코드를 갖습니다. 예를 들어, 다음은 자주 사용되는 몇 가지 명령어입니다:

    1. getstatic: 정적 필드를 가져옵니다.
      • 예: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
    2. ldc: 상수 풀에서 값을 로드합니다.
      • 예: ldc #3 // String Hello, World!
    3. invokevirtual: 인스턴스 메서드를 호출합니다.
      • 예: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
    4. return: 메서드에서 반환합니다.
      • 예: return

    이 명령어들은 스택 기반 구조로 동작하는 JVM에서 실행됩니다. 자바 바이트코드는 명령어와 데이터가 스택을 통해 처리되는 방식으로 작동합니다.

    바이트코드 예제 분석

    앞서 예로 들었던 HelloWorld 프로그램의 main 메서드의 바이트코드를 다시 보겠습니다:

    public static void main(java.lang.String[]);
      Code:
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String Hello, World!
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
    
    

    각 명령어는 다음과 같이 해석됩니다:

    1. 0: getstatic #2
      • getstatic 명령어는 상수 풀에서 정적 필드를 가져옵니다.
      • #2는 상수 풀의 인덱스를 나타내며, java/lang/System.out을 가리킵니다.
    2. 3: ldc #3
      • ldc 명령어는 상수 풀에서 상수를 로드합니다.
      • #3은 상수 풀의 인덱스를 나타내며, "Hello, World!" 문자열을 가리킵니다.
    3. 5: invokevirtual #4
      • invokevirtual 명령어는 인스턴스 메서드를 호출합니다.
      • #4는 상수 풀의 인덱스를 나타내며, java/io/PrintStream.println 메서드를 가리킵니다.
    4. 8: return
      • return 명령어는 메서드를 종료하고 호출한 곳으로 돌아갑니다.

    JVM 스택 기반 동작 방식

    JVM은 스택 기반 가상 머신으로, 대부분의 연산은 스택을 통해 이루어집니다. 예를 들어, 위의 바이트코드가 실행되는 과정은 다음과 같습니다:

    1. getstatic 명령어는 System.out 객체를 스택에 푸시합니다.
    2. ldc 명령어는 "Hello, World!" 문자열을 스택에 푸시합니다.
    3. invokevirtual 명령어는 스택에서 두 개의 항목(System.out 객체와 "Hello, World!" 문자열)을 팝하고, println 메서드를 호출합니다.
    4. return 명령어는 메서드를 종료합니다.

    이와 같이, 자바 바이트코드는 JVM이 자바 프로그램을 실행할 수 있도록 설계된 명령어 집합입니다. 각 명령어는 JVM 스택을 이용해 연산을 수행하며, 클래스 파일에 저장된 바이트코드는 JVM이 프로그램을 해석하고 실행하는 데 필요한 정보를 모두 포함하고 있습니다.

7. 클래스 파일 생성 (Class File Generation)

바이트코드가 생성되면, 이를 .class 파일에 저장합니다. 이 .class 파일은 JVM에 의해 실행될 준비가 된 상태입니다. .class 파일은 자바 클래스 파일 형식을 따르며, 클래스의 바이트코드, 상수 풀, 메서드 및 필드 정보를 포함합니다.

3. 클래스 로딩

3.1. JVM 시작

컴파일된 바이트코드 파일을 실행하려면 JVM을 시작해야 합니다. java 명령을 사용하여 JVM을 시작하고 클래스 파일을 로드합니다.

java HelloWorld

JVM은 HelloWorld.class 파일을 찾아 메모리에 로드합니다.

3.2. 클래스 로더 시스템

JVM의 클래스 로더 시스템은 여러 단계로 이루어져 있습니다.

  • Bootstrap ClassLoader: JVM의 핵심 클래스들을 로드합니다.
  • Extension ClassLoader: 확장 라이브러리를 로드합니다.
  • Application ClassLoader: 사용자가 작성한 애플리케이션 클래스를 로드합니다.

4. 바이트코드 검증

4.1. 검증 단계

JVM은 로드된 바이트코드가 올바른지 검증합니다. 이 과정에서는 바이트코드가 JVM 규칙을 준수하는지 확인합니다. 검증 단계는 다음과 같이 이루어집니다:

  • 파일 형식 검증: 클래스 파일 형식이 올바른지 확인합니다.
  • 바이트코드 검증: JVM 명령어가 올바르게 구성되었는지 확인합니다.
  • 링크 검증: 클래스, 필드, 메서드의 유효성을 확인합니다.

5. 실행 엔진

5.1. 인터프리터

인터프리터는 바이트코드를 한 줄씩 해석하고 실행합니다. 초기 실행 속도는 느리지만, 실행의 유연성을 제공합니다.

5.2. JIT 컴파일러

JIT(Just-In-Time) 컴파일러는 자주 실행되는 바이트코드 부분을 기계어 코드로 변환하여 실행 속도를 향상시킵니다. JIT 컴파일러는 인터프리터와 협력하여 성능을 최적화합니다.

5.3. 가비지 컬렉터

가비지 컬렉터는 사용되지 않는 객체를 자동으로 메모리에서 제거하여 메모리 누수를 방지합니다. 이는 JVM의 메모리 관리를 최적화하는 중요한 기능입니다.

6. 네이티브 메서드 인터페이스 (JNI)

6.1. 네이티브 메서드 호출

자바 프로그램이 네이티브 메서드를 호출해야 할 때, JNI를 사용합니다. JNI는 자바 코드와 네이티브 코드를 연결하는 인터페이스로, C/C++로 작성된 라이브러리를 사용할 수 있게 합니다.

7. 프로그램 실행

7.1. main 메서드 호출

JVM은 모든 준비가 완료되면 main 메서드를 호출하여 프로그램을 실행합니다. 예제에서는 HelloWorld 클래스의 main 메서드를 실행하고, 콘솔에 "Hello, World!"를 출력합니다.

Hello, World!

요약

자바 프로그램이 실행되는 전체 과정을 요약하면 다음과 같습니다:

  1. 자바 소스 코드 작성 (.java 파일)
  2. 컴파일 단계 (javac 명령을 통해 .class 파일 생성)
  3. 클래스 로딩 (JVM의 클래스 로더가 .class 파일을 메모리에 로드)
  4. 바이트코드 검증
  5. 실행 엔진을 통한 실행 (인터프리터와 JIT 컴파일러 사용)
  6. 필요시 네이티브 메서드 호출 (JNI 사용)
  7. 프로그램 실행 (main 메서드 호출)

이 과정에서 각 단계가 서로 유기적으로 작용하여 자바 프로그램이 안전하고 효율적으로 실행되도록 합니다.

JVM 구성 요소

image

  • 클래스 로더 (Class Loader): 자바 클래스 파일을 JVM으로 로드하는 역할을 합니다. 클래스 파일을 읽고 그 유효성을 검사한 후 실행할 준비를 합니다.

  • 런타임 데이터 영역 (Runtime Data Areas): 자바 프로그램을 실행하기 위해 JVM이 할당하는 메모리 영역입니다. 주요 데이터 영역에는 다음이 포함됩니다:

    • 메소드 영역 (Method Area): 클래스 구조, 런타임 상수 풀, 필드 및 메소드 데이터를 저장합니다.
    • **힙 (Heap)**힙(힙): 모든 클래스 인스턴스와 배열이 할당되는 런타임 데이터 영역입니다.
    • 자바 스택 (Java Stacks): 각 스레드마다 존재하며, 프레임을 저장합니다. 프레임은 지역 변수와 부분 결과를 포함하며 메소드 호출 및 반환을 담당합니다.
    • PC 레지스터 (PC Register): 각 스레드마다 존재하며 현재 실행 중인 JVM 명령의 주소를 저장합니다.
    • 네이티브 메소드 스택 (Native Method Stacks): 네이티브 메소드 실행에 사용됩니다.인지된 그리드(네이티브 메서드 스택): 사용된 것으로 간주되었습니다.
  • 클래스 로더 (Class Loader): 자바 클래스 파일 로드.

    • Bootstrap ClassLoader jre의 lib폴더에 있는 [rt.jar](https://sas-study.tistory.com/rt.jar) 파일을 뒤져 기본 자바 API 라이브러리를 로드합니다. 가장 최우선으로 로드됩니다.
    • Extension ClassLoader jre의 lib 폴더에 있는 ext 폴더에 있는 모든 확장 코어 클래스파일들을 로드합니다. 최근에는 Platform ClassLoader라고 부르기도 합니다. Bootstrap ClassLoader의 child 입니다. Extension 클래스 로더는 jdk 확장 디렉토리(JAVA_HOME/lib/ext 디렉토리 혹은 [java.ext.dirs](https://sas-study.tistory.com/java.ext.dirs) 에 저장된 경로)에서 로드됩니다.
    • Application ClassLoader Extension ClassLoader의 child이며 시스템 클래스로더(System ClassLoader)라고도 불립니다. 어플리케이션 레벨에 있는 클래스들을 로드합니다. 즉, 사용자가 직접 정의한 클래스파일들을 로드합니다. Classpath 환경변수에 있는 클래스 파일이나 -classpath 또는 -cp 명령어 옵션이 있는 파일들을 로드합니다.
  • 런타임 데이터 영역 (Run-Time Data Areas): 프로그램 실행 중에 사용되는 다양한 런타임 데이터 영역

    • The pc Register

      • 각 스레드마다 존재하며 현재 실행 중인 JVM 명령의 주소를 저장
    • Java Virtual Machine Stacks

      • 프레임을 저장합니다
      • 로컬 변수와 부분적인 결과를 보유하고 메서드 호출 및 반환에 사용되는 스택
    • 힙 (Heap)

      • 모든 Java Virtual Machine 스레드 간에 공유
      • 모든 클래스 인스턴스 및 배열에 대한 메모리가 할당되는 런타임 데이터 영역
      • 객체의 힙 저장소는 자동 저장소 관리 시스템(가비지 수집기라고도 함)에 의해 회수됩니다. 객체는 명시적으로 할당 해제되지 않습니다.
    • Method Area

      • 모든 Java Virtual Machine 스레드 간에 공유되는 메소드 영역
      • 런타임 상수 풀, 필드 및 메서드 데이터와 같은 클래스별 구조와 클래스 및 인터페이스 초기화와 인스턴스 초기화(§2.9)에 사용되는 특수 메서드를 포함하여 메서드 및 생성자에 대한 코드를 저장
      • 논리적으로 힙의 일부이지만 간단한 구현에서는 가비지 수집이나 압축을 선택하지 않을 수도 있습니다.
    • Run-Time Constant Pool

      • 컴파일 타임에 알려진 숫자 리터럴부터 런타임에 확인해야 하는 메서드 및 필드 참조에 이르기까지 여러 종류의 상수가 포함되어 있습니다.
      • 각 런타임 상수 풀은 Java Virtual Machine의 메서드 영역(§2.5.4)에서 할당됩니다.
    • Native Method Stacks

      • native 메서드(Java 프로그래밍 언어 이외의 언어로 작성된 메서드)를 지원하기 위해 일반적으로 "C 스택"이라고 하는 기존 스택을 사용할 수 있습니다.
  • 실행 엔진 (Execution Engine): 바이트코드 실행.

    • 인터프리터 (Interpreter): 바이트코드 직접 실행.
    • JIT 컴파일러 (JIT Compiler): 바이트코드 네이티브 코드 변환.
  • 가비지 컬렉터 (Garbage Collector): 메모리 관리.

  • 자바 네이티브 인터페이스 (JNI): 네이티브 애플리케이션 및 라이브러리 호출.

  • 네이티브 메소드 라이브러리 (Native Method Libraries): 플랫폼별 라이브러리 사용.

  • 레퍼런스

Java 버전별 GC의 개략적 특징

[Java 1.0 ~ 1.4]

Serial GC

알고리즘: Mark-Sweep-Compact

특징:

  • 단일 스레드로 실행
  • GC 작업 동안 애플리케이션이 중단됨 (stop-the-world)

Parallel GC (Java 1.4 ~ )

알고리즘: Parallel Mark-Sweep-Compact

특징:

  • 다중 스레드를 사용하여 GC 작업을 병렬로 수행
  • 멀티 코어 시스템에서 성능 향상

[Java 1.5 ~ ]

Concurrent Mark-Sweep Collector

알고리즘: Concurrent Mark-Sweep

특징:

  • 애플리케이션 중단 시간을 줄이기 위해 설계됨
  • Mark 단계는 동시에 수행되지만, Sweep 단계는 stop-the-world
  • 고주파수의 stop-the-world 이벤트를 줄여서 애플리케이션 반응성을 향상
  • Java 17부터 지원 X

[Java 1.7 ~ ]

G1 Garbage Collector

알고리즘: Garbage-First (G1)

특징:

  • 대부분의 stop-the-world 단계를 줄이기 위해 설계됨
  • 대규모 heap에 대해 더 효율적
  • Full GC를 피하기 위한 다양한 최적화 포함
  • Default GC로 사용 가능
  • Java 1.9부터 G1이 기본 GC임

[Java 11 ~ ]

Z Garbage Collector

알고리즘: Region-based, concurrent

특징

  • 매우 짧은 중단 시간 (몇 밀리초 이내)
  • 대부분의 작업을 애플리케이션 스레드와 동시에 수행
  • G1 GC와 ZGC가 Java 18 ~ 에서 주력

G1 Garbage Collector Heap Layout

Untitled

  • Red: eden regions
  • Red with ‘S’: survivor regios
  • Blue old regions
  • Blue with ‘H’

G1 GC의 Heap Layout의 region이 다른 GC의 Heap Layout과 다른점은 비연속적인 (noncontinous) 공간에 할당된다는 것이다. (그림참고)

얻는 이점은?

[키워드 정리]

Mark-Sweep-Compact

Parallel Mark-Sweep-Compact

Concurrent Mark-Sweep

Garbage-First (G1)

stop-the-world

Reference

[HotSpot Virtual Machine Garbage Collection Tuning Guide](https://docs.oracle.com/en/java/javase/17/gctuning/garbage-first-g1-garbage-collector1.html#GUID-15921907-B297-43A4-8C48-DC88035BC7CF)

👼 개인 활동을 기록합시다.

개인 활동 페이지

🧑‍🧑‍🧒‍🧒 그룹 활동을 기록합시다.

그룹 활동 페이지

🎤 미니 세미나

미니 세미나

🤔 기술 블로그 활동

기술 블로그 활동

📚 도서를 추천해주세요

추천 도서 목록

🎸 기타

기타 유용한 학습 링크

Clone this wiki locally