[JAVA] 자바의 실행 과정

이권민·2025년 9월 30일

JAVA

목록 보기
1/3

목차

  1. 실행 과정 개요
  2. Java Compiler
    • WORA (Write Once, Run Anywhere)
  3. Class Loader
  4. Runtime Data Area
  5. Execution Engine
    • Interpreter
    • JIT Compiler
    • Garbage Collection
    • Native Method Interface
  6. 예외 처리
  7. 프로그램 종료

실행과정

💡 JVM은 자바 프로그램을 효율적으로 실행하기 위해
컴파일(바이트코드 변환) → 클래스 로딩 → 메모리 할당(Runtime Data Area) →
실행(인터프리터+JIT) → 가비지 컬렉션 → 예외 처리 → 종료 단계를 수행


Java Compiler

자바 소스 파일(.java)을 JVM이 이해할 수 있는 바이트코드(.class)로 변환.

  • 구문 오류·타입 오류 확인

  • 변환 과정에서 발생하는 오류 = 컴파일 타임 에러

  • 바이트코드는 OS 독립적이며 JVM이 설치된 환경이라면 동일하게 실행 가능

  • WORA (Write Once, Run Anywhere): 한 번만 컴파일하면 중간코드인 바이트코드 생성, HW/OS 무관 실행(jvm 내에서)

  • 자바는 컴파일(바이트코드 변환) + 인터프리터(바이트코드 런타임 실행) 혼합 방식을 사용

  • 과정: Java Source File(.java) → (Java Compiler) → Byte Code(.class)

    🔎 더 알아보기
    • 바이트 코드는 JVM에서만 사용할 수 있는 중간 코드
    • 하드웨어/OS 계층에서 실행하려면 JVM이 기계어로 변환 필요
    • C: HW 기종별로 기계어로 컴파일(OS,HW에 맞게 실행파일 생성) → 플랫폼 종속적
    • Java: 중간 단계 언어(바이트코드) → JVM이 해석 → 플랫폼 독립적 실행
    • JVM만 각 OS에 설치되어 있으면 HW 종류와 상관없이 실행 가능

Class Loader

바이트코드를 JVM 메모리(Runtime Data Area)에 적재. (Runtime 시점에 로드)

  • 메모리 로딩: JVM이 자바 프로그램을 실행하기 위해 필요한 클래스와 객체들을 메모리에 옮기는 과정
  • 과정: Java Byte Code(class) → Class Loader에서 수행
🔎 더 알아보기
  • 세부과정: Loading → Linking → Initialization

    • Loading: *.class 읽어 적절한 binary data를 만들고 runtime data area의 method area에 저장. (static main() 먼저)
    • Linking: Verify(검증) → Prepare(필드/메소드 메모리 준비) → Resolution(참조 주소 변환)
      • Verify: Java language specification (자바 언어 명세)기반 검사. 무결성 검증.
      • Prepare: class가 필요한 메모리를 할당. (field, method, interface 등) static field는 기본값으로 생성되고 초기화.
      • Resolve : 모든 symbolic reference(이름으로만 참조)들을 direct reference(실제 메모리 참조)로 변경.
    • Initialization: static 변수 초기화(코드에 정의된 값) 및 static 블록 실행(클래스가 처음 로드될 때 딱 한 번 실행)
      // InitDemo.java
      public class InitDemo {
        static int count = 10; // Prepare: 기본값 0 → Initialization: 10
        static {
            System.out.println("static block executed");
            count += 5;
        }
        public static void main(String[] args) {
            System.out.println(count); // 15
        }
      }

Runtime Data Area

JVM이 프로세스로써 수행되기 위해 OS로부터 할당받은 메모리 공간.

  • 주요 구성(저장 목적 기준 분류): Method Area, Heap, Stack, PC Register, Native Method Stack
🔎 더 알아보기
  • Method Area: 클래스 정보, static 변수, 상수 저장 (공유 영역)
  • Heap Area: new 명령어로 생성된 인스턴스와 객체와 배열 저장, 크기에 맞게 메모리 할당. 공간 부족 시 GC(Garbage Collection) 실행 (공유영역)
  • Stack Area: 각 스레드마다 존재, Method안에서 사용되는 값들(매개변수, 지역변수, 리턴 값 등)이 저장되는 구역. 메소드 호출 시 LIFO 로 하나씩 생성. 메서드 실행이 완료되면 LIFO로 하나씩 제거. 스택 프레임 관리
    • 스택 프레임 : arguments, return address, local variable 등에 대한 정보를 담고 있음.
  • PC Register: 각 스레드마다 하나씩 생성. 현재 실행 중인 JVM 명령어 주소
  • Native Method Stack: 각 스레드마다 하나씩 생성. 다른 언어(C/C++ 등)의 네이티브 메소드 실행 저장. JNI(Java Native Interface)라는 표준 규약을 제공

Execution Engine

로드된 바이트코드를 실제 해석, 실행하는 엔진. 이 과정에서 ByteCode가 BinaryCode로 변경.

  • Interpreter: 한 줄씩 실행(기본)

  • JIT Compiler: 반복 코드 기계어 변환 후 캐시 실행(자주 실행_HotSpot 모니터)

  • Garbage Collection: 불필요 객체 정리

  • JNI(Java Native Interface): 자바 코드와 네이티브 코드(C, C++ 등) 간의 상호작용을 가능하게 하는 표준 인터페이스. 네이티브 코드와 연동

  • 네이티브 코드에 대한 해석은 Native Method Interface, Native Method Libaray에서 수행

    • 이때 JVM은 스택 기반의 가상머신으로 동작하여 스택 프레임을 사용하여 메서드 호출과 반환을 관리.
🔎 더 알아보기

Interpreter

  • 바이트코드를 한 줄씩 해석하고 실행

  • 장점: 즉시 실행 가능

  • 단점: 전체 실행 속도는 느림

  • 과정
    이전에 할당된 메모리에서 바이트코드 읽어옴

    -> 인터프리터를 통해 한줄 씩 읽고 해석

    -> 바이트 코드를 해석하는 동안 인터프리터는 필요한 메모리 및 리소스를 할당하고 해당 바이트 코드에 따라 실행

    -> 바이트 코드가 실행되는 동안 인터프리터는 필요한 경우에 따라 라이브러리 및 다른 클래스를 로드하고 사용

    -> 인터프리터는 바이트 코드를 순차적으로 해석하면서 프로그램을 실행

JIT Compiler(Just In Time Compiler)

  • 전체를 컴파일 하지 않고, 인터프리터가 자주 사용하는메소스의 byte 코드를 컴파일하여 binary 코드로 변환 후 코드 캐시에 저장

  • 이후 동일 코드 실행 시 인터프리터로 해석하지 않고 캐시된 binary 코드(기계어) 사용 → 속도 향상

  • 해당 기술은 프로그램을 실행하는 동안 실시간으로 바이트 코드를 기계어로 변환하여 성능을 향상.

  • 과정

    이전에 생성하고 할당된 메모리를 기반으로 바이트 코드를 읽어옴

    -> JIT 컴파일러는 실행 중인 프로그램을 모니터링하면서 빈번하게 실행되는 코드 블록을 식별.

    -> 해당 코드 블록은 인터프리터에 의해 실행될 때마다 JIT 컴파일러에 의해 기계어로 변환

    -> JIT 컴파일러는 변환된 기계어 코드를 캐시에 저장.

    -> 이후 동일한 코드 블록이 다시 호출되면, 캐시에 저장된 기계어 코드를 실행하여 인터프리터에 비해 훨씬 빠른 실행 속도를 제공.

Garbage Collection (GC)

  • Heap을 대상으로 필요없는 객체(uncreachable)들을 자동으로 감시, 메모리 해제, 메모리 관리 최적화.
    • 객체가 null 인 경우
    • 블럭의 local 객체 (블럭 내부에서 생성된 객체)
    • 부모 객체가 null인 자식 객체
  • 과정
    • Marking : 모든 객체 스캔, 참조 중인 객체 표시
    • Sweeping : 표시되지 않은 객체 제거
    • Compacting : 메모리에서 제거된 객체들로 인해 생긴 빈 공간을 모아서 압축. 메모리 단편화 해소(외부 단편화), 연속된 메모리 공간을 확보

JNI (Java Native Interface)

  • Java ↔ C/C++ 네이티브 코드 호출, 상호 작용 지원.
  • JVM이 바이트 코드를 해석하고 실행하는 동안 네이티브 메서드를 호출하고 해당 코드가 네이티브 코드로 실행될 수 있도록 함.

Native Method Interface

  • JVM 내부에서 네이티브 코드와 연결하기 위한 내부 인터페이스
  • 자바에서 네이티브 메소드를 호출할 때, 메서드 시그니처와 실제 네이티브 코드의 주소를 연결해줌
  • JVM이 “이 네이티브 메서드가 어디에 구현돼 있는지” 알 수 있도록 매핑

Native Method Library

  • C 또는 C++과 같은 특정 프로그래밍 언어로 구현된 네이티브 메서드의 컬렉션. 실제 네이티브 코드가 들어 있는 라이브러리 파일. 추가 기능이나 시스템 수준의 리소스에 액세스 할 수 있는 기능을 제공.
  • 해당 부분은 JVM이 바이트 코드를 해석하는 동안 호출되고 실행.

예외 처리

실행 중 예외 발생 시 JVM은 예외 객체를 생성하고 적절한 핸들러를 찾아 처리.

  • JVM은 예외 처리를 위해 예외 핸들러를 사용하며, 예외가 발생했을 때 해당 예외를 처리할 수 있는 예외 핸들러를 찾아 처리
  • 처리 불가 시 호출 스택을 따라 상위 메서드로 전파
  • 여러 단계 전파될 경우 Exception Chaining 발생
🔎 더 알아보기
  1. 예외 발생 → 예외 객체 (예외의 유형, 메시지 및 예외가 발생한 위치 정보) 생성
  2. 예외 처리기(Exception Handler) 탐색
    • 예외 처리기: 예외를 처리하는 코드 블록으로, 예외를 처리하는 방법을 정의
  3. 처리 불가 시 호출 스택을 따라 상위 메서드로 전파
  4. 상위 호출자에게 예외 전달 → 처리기 발견 시 실행
  5. 처리 불가 시 다시 전파 → 예외 체인(Exception Chaining) 발생

프로그램 종료

프로그램 실행이 완료되거나 명시적으로 종료되면 JVM은 자원을 해제하고 종료 코드를 반환.

🔎 더 알아보기
  1. 열린 파일·리소스 정리
  2. 종료 코드 반환 (성공/실패 여부)
  3. 메모리와 모든 자원 해제


🔗 참고

profile
이것저것이것 개발자

0개의 댓글