Java 동작관련 정리

HWKoo·2023년 8월 16일

JDK / JRE / JVM 이란?

이미지 출처 : https://www.techcrashcourse.com/2017/02/difference-between-jdk-jre-and-jvm.html

1. JDK (Java Development Kit)

  • JDK는 Java 애플리케이션의 개발 및 실행에 필요한 Java 모듈 세트
  • Java 프로그램을 컴파일하기 위해 javac 컴파일러를 제공하며 JDK는 소스 코드를
    JRE에서 쉽게 실행할 수 있는 특정 형식으로 변환해야함
  • JDK에는 JRE, 컴파일러, jheap, javadoc 등과 같은 Java 개발 도구가 포함되며 JRE의 상위 집합

2. JRE (Java Runtime Environment)

  • JRE는 실제로 Java 프로그램을 실행하는 JVM의 구현
    Java 애플리케이션을 실행하는데 필요한 핵심 클래스 및 라이브러리와 JVM을 결합함
  • JRE는 Java 애플리케이션을 실행하는데 필요하지만 Java 애플리케이션 개발에 필요한
    모듈을 포함하지 않음
  • JRE는 JVM의 상위 집합이지만 JDK의 하위 집합

3. JVM (Java Virtual Machine)

  • JVM(Java Virtual Machine)은 자바 프로그래밍 언어로 작성된 애플리케이션을
    실행하기 위한 가상 머신
  • 자바 코드를 컴파일하여 바이트 코드(.class)로 만들면 이 코드가 자바 가상머신 환경에서 실행됨
  • 하나의 바이트 코드(.class)로 모든 플랫폼에서 동작하도록 할 수 있음

3.1. JVM의 구조

이미지 출처 : https://honbabzone.com/java/java-jvm/

위의 그림과 같이 JVM은 3가지 서브 시스템으로 구분할 수 있다.

  • 클래스 로더 서브 시스템 (Class Loader Sub System)
  • 실행 데이터 영역 (Runtime Data Areas)
  • 실행 엔진 (Execution Engine)

3.1.1. 클래스 로더 서브 시스템 (Class Loader Sub System)

  • 클래스 로더 서브 시스템은 자바 애플리케이션 실행 시 클래스 파일을 로드하고,
    해당 클래스들을 JVM 내부로 적재하는 역할을 담당하는 컴포넌트
  • 자바의 동적 로딩(Dynamic Loading) 및 클래스의 네임스페이스 관리 등을 관리하며,
    이를 통해 자바 언어의 유연성과 모듈성을 제공

3.1.1.1. 로딩 (Loading)

  • 클래스 파일(.class)을 파일 시스템이나 네트워크 등에서 읽어들여 JVM 내로 로드

1) 부트스트랩 클래스 로더(Bootstrap Class Loader)

  • JVM 내부의 핵심 라이브러리 및 클래스들을 로드하는 로더
  • 일반적으로 네이티브 코드로 구현되어 있음

2) 익스텐션 클래스 로더(Extension Class Loader)

  • 특정 확장 기능과 관련된 클래스들을 로드
  • 주로 JAVA_HOME/ext 디렉토리 아래의 클래스들을 관리

3) 애플리케이션 클래스 로더(Application Class Loader)

  • 사용자가 작성한 애플리케이션 클래스들을 로드
  • 애플리케이션 클래스패스(classpath)에 있는 클래스들을 관리

4) 커스텀 클래스 로더(Custom Class Loaders)

  • 사용자가 직접 작성하여 클래스 로딩 동작을 확장하거나 커스터마이즈할 수 있는 클래스 로더
  • 보통 애플리케이션 특정 클래스 로딩 요구를 처리하기 위해 사용

3.1.1.2. 링크 (Linking)

  • 로드한 클래스 파일을 검증(Verification), 준비(Preparation), 해결(Resolution)하는 과정을 포함
  • 해결 단계에서는 클래스가 다른 클래스를 참조할 때 실제로 그 참조되는 클래스를 찾아서
    연결해주는 역할을 수행

3.1.1.3. 초기화 (Initialization)

  • 클래스의 정적 필드(static fields)와 정적 블록(static blocks)이 초기화 됨
  • 필요한 리소스를 할당하거나 초기값을 설정하는 작업을 수행

3.1.1.4. 네임스페이스 관리

  • 클래스 로더는 클래스의 네임스페이스를 관리하여 같은 이름의 클래스라도 서로 다른
    로더에 의해 로드된 경우 다른 클래스로 취급
  • 각 클래스 로더마다 독립된 클래스들을 유지할 수 있음

3.1.2. 실행 데이터 영역 (Runtime Data Areas)

  • JVM이 프로그램을 실행하는 동안 데이터를 저장하고 관리하는 메모리 영역
  • JVM이 프로그램을 로드하고 실행하는 동안 필요한 데이터를 보관하며, 프로그램의
    실행 과정에서 변화할 수 있음

3.1.2.1. 메소드 영역 (Method Area)

  • 클래스 정보, 정적 변수(static variables), 상수 등을 저장하는 영역
  • 모든 스레드가 공유하며, 클래스 로딩 시에 초기화 됨

3.1.2.2. 힙 (Heap)

  • 동적으로 생성된 객체와 배열을 저장하는 영역
  • 객체는 이곳에 할당되며, GC(Garbage Collector)에 의해 관리됨
  • 메모리 할당과 회수가 동적으로 이루어지므로 프로그램의 실행에 따라 크기가 변할 수 있음

3.1.2.3. 스택 (Stack)

  • 각 스레드마다 생성되며, 메소드 호출과 관련된 데이터를 저장
  • 각 메소드 호출 시에 호출 스택 프레임(call frame)이 생성되어 지역 변수, 매개 변수,
    메소드 호출 정보 등을 저장
  • 메소드 실행이 끝나면 해당 스택 프레임이 제거됨

3.1.2.4. PC 레지스터 (Program Counter Register)

  • 현재 실행 중인 JVM 명령어의 주소를 저장하는 영역
  • 각 스레드마다 별도의 PC 레지스터가 존재하며, 다음에 실행될 명령어의 위치를 추적함

3.1.2.5. 네이티브 메소드 스택 (Native Method Stack)

  • 네이티브 코드(네이티브 메소드)를 실행하는데 사용되는 스택
  • 자바 코드가 아닌 다른 언어로 작성된 네이티브 메소드의 호출과 관련된 데이터를 저장

3.1.3. 실행 엔진 (Execution Engine)

  • 자바 프로그램의 바이트코드를 실제 기계 코드로 변환하고 실행하는 역할을 수행하는
    핵심 컴포넌트
  • 실행 엔진은 자바 코드를 실제로 실행시키는 역할을 하며, 프로그램의 동작을 담당

3.1.3.1. 바이트코드 로더 (Bytecode Loader)

  • 클래스 로더가 클래스 파일을 메모리에 로드하면, 실행 엔진은 해당 클래스 파일의
    바이트코드를 읽어들임
  • 바이트코드는 자바 컴파일러에 의해 생성된 중간 형식의 코드로, 플랫폼 독립적인 형태

3.1.3.2. 바이트코드 해석 (Opcode Interpreter)

  • 바이트코드를 해석하여 JVM이 이해하고 실행할 수 있는 기계 코드로 변환
  • 바이트코드의 각 명령어(옵코드)를 읽어 해당하는 동작을 수행하며 이것이 가장 기본적인 실행 방식
  • 인터프리터(Interpreter)라고도 함

1) 인터프리터 (Interpreter)

  • 바이트코드를 순차적으로 해석하고 실행하는 방식
  • 단순하고 이식성이 좋지만 느릴 수 있음

3.1.3.3. 실행 최적화 (Execution Optimization)

  • 인터프리터 방식은 간단하고 이식성이 좋지만, 속도가 느릴 수 있음
  • 실행 엔진은 반복적으로 수행되는 코드를 식별하고, 이를 더 효율적으로 실행할 수 있는
    최적화 기법을 적용함
  • 자주 사용되는 코드에 대한 캐싱, 인라이닝(Inlining), JIT 컴파일러를 통한 동적 컴파일
    등의 기술로 이루어짐

1) JIT 컴파일러 (Just-In-Time Compiler)

  • 인터프리터와 함께 사용되며, 실행 중에 바이트코드를 기계 코드로 컴파일하여 실행하는 방식
  • 자주 사용되는 코드를 미리 컴파일하여 성능 향상을 도모

2) AOT 컴파일러 (Ahead-of-Time Compiler)

  • 프로그램을 실행하기 전에 전체 코드를 기계 코드로 컴파일하는 방식
  • 초기 실행 속도를 향상시킬 수 있지만, 이식성이 떨어질 수 있음

4. Binary Code / Bite Code

Binary Code

  • 컴퓨터가 인식할 수 있는 0과 1로 구성된 이진코드
  • 주로 컴파일러에 의해 생성되며, 소스 코드를 컴퓨터가 처리하기 쉬운 기계어로 변환

Bite Code

  • 자바 소스 코드를 컴파일한 결과물
  • JVM에서 실행되는 중간 언어
  • 플랫폼에 독립적이며, 어떤 운영체제에서든 JVM을 통해 실행될 수 있음

👉자바코드의 Bite Code 확인하는 법

1) 자바소스를 컴파일하여 .class 를 생성한다.

// 예제 자바 소스
public class Main {
    public static void main(String[] args) {
        System.out.println("Hello and welcome!");
    }
}

2) 생성한 .class 의 위치에서 javap(역어셈블) 커맨드를 사용.

3) 출력된 Bite Code 확인

// Main.class의 Bite Code
public class Main
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#15         // java/lang/Object."<init>":()V
   #2 = Fieldref           #16.#17        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #18            // Hello and welcome!
   #4 = Methodref          #19.#20        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Class              #21            // Main
   #6 = Class              #22            // java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               main
  #12 = Utf8               ([Ljava/lang/String;)V
  #13 = Utf8               SourceFile
  #14 = Utf8               Main.java
  #15 = NameAndType        #7:#8          // "<init>":()V
  #16 = Class              #23            // java/lang/System
  #17 = NameAndType        #24:#25        // out:Ljava/io/PrintStream;
  #18 = Utf8               Hello and welcome!
  #19 = Class              #26            // java/io/PrintStream
  #20 = NameAndType        #27:#28        // println:(Ljava/lang/String;)V
  #21 = Utf8               Main
  #22 = Utf8               java/lang/Object
  #23 = Utf8               java/lang/System
  #24 = Utf8               out
  #25 = Utf8               Ljava/io/PrintStream;
  #26 = Utf8               java/io/PrintStream
  #27 = Utf8               println
  #28 = Utf8               (Ljava/lang/String;)V
{
  public Main();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0
  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String Hello and welcome!
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 3: 0
        line 4: 8
}
SourceFile: "Main.java"

5. Java Compile

Java Compile 의 과정

  1. 소스 코드 작성 :
    ".java" 확장자를 가진 자바 소스 코드를 작성

  2. 컴파일러 실행 :
    작성한 소스 코드를 컴파일하기 위해 자바 컴파일러(javac)를 실행
    컴파일러는 소스 코드를 읽어들이고 문법적으로 올바른지 검사한 후에 중간 형태인 바이트 코드로
    변환하며 문제가 있다면 컴파일 오류가 발생함

  3. 바이트 코드 생성 :
    컴파일러가 소스 코드를 분석하여 중간 언어인 바이트 코드로 변환
    이 바이트 코드는 JVM이 이해하고 실행할 수 있는 형식

  4. 바이트 코드 파일 생성 :
    컴파일러가 생성한 바이트 코드는 .class라는 확장자를 가진 파일에 저장됨
    이 파일은 클래스 단위로 생성되며, 하나의 자바 소스 파일에 여러 개의 클래스가 있다면
    각 클래스마다 별도의 .class 파일이 생성

  5. 바이트 코드 실행 :
    생성된 바이트 코드는 JVM에서 실행됨
    JVM은 플랫폼에 종속되지 않는 가상 머신으로, 바이트 코드를 실행하면서
    자바 프로그램이 실제로 동작

Java Compile 실행

// 예제 자바 소스
public class Main {
    public static void main(String[] args) {
        System.out.println("Hello and welcome!");
    }
}

1) 컴파일 할 자바 소스가 있는 위치로 이동한다.

2) javac 파일명.java 명령어를 입력하여 컴파일한다.

3) 컴파일 된 .class 파일 확인

4) .class 파일 실행

profile
천천히 그리고 꾸준히

0개의 댓글