코딩관계론

자바 어떻게 실행되는가? 본문

개발/Java

자바 어떻게 실행되는가?

개발자_티모 2024. 6. 2. 17:09
반응형

개요

프로그램을 실행하기 위해선 사람이 읽을 수 있는 언어(코드)에서 기계(바이너리)로 번역하는 과정을 거쳐야 한다. "이 과정을 컴파일 한다".라고 말한다. 그럼 자바에서는 어떤 방식으로 이 과정이 수행되는지 알아보자

 

자바가 실행되기 위해선 아래의 과정을 수행해야만 한다.

  1. 자바 언어로 된 자바 파일을 작성한다
  2. javac 명령어로 해당 파일을 컴파일하여 바이트 파일로 변환한다
  3. 바이트 파일을 JVM에서 로드한 후 컴퓨터가 읽을 수 있도록 기계어로 변환한다
  4. 실행

 

Java 컴파일

컴파일이라는 사전적 의미는 엮다라는 의미를 가지고 있다. 즉 내가 .java파일을 바이트 파일로 엮는 작업을 컴파일한다 라고 부른다. 그럼 이 컴파일이라는 작업을 수행하는 녀석이 바로 컴파일러다. 자바에서는 javac가 컴파일러가 되는데 여러분이 JDK를 설치하면 같이 설치되서 따로 설치 할 필요는 없다.

 

바이트 파일 획득 

그럼 컴파일러가 변환한 바이트 파일을 확인하기 위해선 자바 프로그램을 작성해야 함으로 아래와 같이 간단하게 작성했다.

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

 

그후 아래의 명령어를 입력하면 같은 디렉토리에 .class 파일이 만들어진 것을 확인할 수 있다.

javac compileJava.java

 

그럼 이제 .class 파일이 만들어지기 까지 javac가 하는 동작과정을 살펴보겠다.

Javac 동작과정

1.  소스 파일 파싱

javac는 소스 파일을 읽고 구문 분석(파싱)하여 *구문 트리(Syntax Tree)를 생성합니다. 이 과정에서 소스 코드의 문법이 올바른지 검사합니다. 문법 오류가 있을 경우 컴파일러는 오류 메시지를 출력하고 컴파일 과정을 중단합니다.

2. 구문 트리 분석 및 변환

구문 트리를 바이트코드 생성에 적합한 형태로 변환합니다. 이 과정에서 타입 검사(type checking)와 같은 다양한 검증 작업이 수행됩니다. 예를 들어, 변수의 타입이 올바른지, 메서드 호출이 적합한지 등을 검사합니다.

Javac 결과

해당 과정이 문제 없이 완료됐으면 아래의 바이트 파일인 ".class"를 확인할 수 있고, 아래의 명령어를 통해서 실행할 수 있다.

//java -cp <classpath> <package.MainClassName>
java -cp <classpath> <package>.compileJava

우리는 위의 과정을 통해  javac 컴파일러를 사용해 .java 파일을 .class 파일로 컴파일했다.

 

 

*자바에서는 컴파일이 두 번 이루어 진다. 첫번째는 위의 과정에서 바이트 코드를 획득하는 과정이고, 이 바이트 코드를 기계어로 번역하는 과정이 필요하다. 이 과정을 JVM에서 수행한다. 

 

JVM 요약도

 

클래스로더

클래스 로더는 클래스 파일(.class)을 읽어 메모리에 로드하고, 클래스를 동적으로 로드하여 자바 가상 머신에서 실행될 수 있도록 합니다. 클래스로더의 절차는 1.로드 2. 연결 3.초기화로 나뉠 수 있습니다. 다음으로는 각 과정을 자세하게 알아보겠습니다.

 

1. 로드

로드는 이진파일 이름을 파일 시스템의 경로로 변환하여 해당 파일을 읽어서 메모리로 로드하는 작업입니다.

앞서 우리는 java 명령어로 바이트 파일을 실행하기 위해서 class path와 package.main class의 이름을 줬습니다. 이때 주어지는 경로가 바로 이진 이름인데 클래스 로더가 이를 파일 시스템의 경로로 변환하고, 이 파일을 메모리로 로드합니다.

//클래스의 이진 이름을 확인하는 방법
public class compileJava {
    public static void main(String[] args) {
        String binaryName = compileJava.class.getName();
        System.out.println("이진 이름: " + binaryName);
        System.out.println("Hello, World!");
    }
}

 

2. 링킹

링킹은 파일의 유효성을 검사하고,  클래스 변수의 값을 초기화하고, 인터페이스에 의한 참조를 실제 메모리 참조로 변경합니다.

클래스 변수가 기본값으로 초기화됩니다. 예를 들어 'int'타입 변수는 0으로 참조타입 변수는 null로 초기화됩니다.

public class LinkingInitializationExample {
	static int staticVariable; // 기본값 0으로 초기화
    

    public static void main(String[] args) {
        System.out.println("Main Method Start");
        System.out.println("Static Variable (before initialization): " + staticVariable);
        
        // 인스턴스 생성
        LinkingInitializationExample example = new LinkingInitializationExample();
    }
}

 

3. 초기화

초기화에서 클래스 로더가 로드한 클래스의 초기화를 담당합니다.

클래스 로더가 실제 클래스를 메모리에 로드한 후 static 블록을 실행하여 static 변수 실제 값으로 초기화합니다.

public class LinkingInitializationExample {
    // static 변수 선언
    static int staticVariable;

    // static 초기화 블록
    static {
        System.out.println("Static Initialization Block");
        staticVariable = 42; // 실제 값으로 초기화
    }

    public static void main(String[] args) {
        System.out.println("Main Method Start");
        System.out.println("Static Variable (before initialization): " + staticVariable);
        
        // 인스턴스 생성
        LinkingInitializationExample example = new LinkingInitializationExample();
        
        System.out.println("Instance Variable: " + example.staticVariable);
        
    }
}

 

클래스로더 종류 

Bootstrap Class Loader:

  • 가장 기본적인 클래스 로더입니다.
  • 자바 런타임 환경의 핵심 클래스(JDK 내부 클래스, 예: java.lang 패키지의 클래스들)를 로드합니다.
  • 네이티브 코드로 구현되어 있으며, JVM에 내장되어 있습니다.
  • 클래스 경로는 JVM의 -Xbootclasspath 옵션을 통해 설정할 수 있습니다

Extension Class Loader (또는 Platform Class Loader):

  • 확장 클래스 로더라고도 불립니다.
  • JAVA_HOME/lib/ext 디렉토리 또는 java.ext.dirs 시스템 속성에 지정된 경로에 있는 클래스들을 로드합니다.
  • 자바 확장 API(예: JCE, Java 3D)와 같은 추가 라이브러리를 로드합니다.

Application Class Loader (또는 System Class Loader)

  • 애플리케이션 클래스 로더라고도 불립니다.
  • 애플리케이션의 클래스패스에 지정된 디렉토리 및 JAR 파일에 있는 클래스를 로드합니다.
  • 일반적으로 클래스 경로는 CLASSPATH 환경 변수 또는 java 명령의 -cp 옵션을 통해 설정됩니다.

이러한 절차로 우리는 Application Class Loader를 이용해 main class를 실행할 수 있었습니다

 

바이트코드를 기계어로

컴퓨터가 프로그램을 실행하려면 기계어로 변경되어야 한다. 이 바이트 코드를 기계어로 변경하는 것도 JVM의 역활이다.

JVM에서 기계어로 변경하기 위해서 두 가지 컴파일 방식을 제공한다.

  1. 해석(Interpretation:
    • 인터프리테이션(Interpretation): JVM은 바이트 코드를 한 줄씩 읽어들여 해당 명령어에 대한 기계어를 직접 실행합니다. 이 방식은 간단하지만, 반복적인 명령어의 실행으로 인해 성능이 저하될 수 있습니다.
  2. JIT 컴파일(JIT Compilation):
    • JIT 컴파일러는 바이트 코드를 실행하기 전에, 해당 바이트 코드를 기계어로 직접 변환하는 과정을 거칩니다. 이렇게 변환된 기계어 코드는 CPU가 직접 실행할 수 있습니다. JIT 컴파일러는 반복적으로 실행되는 코드를 식별하여 해당 코드를 기계어로 컴파일하고, 이후에는 이전에 컴파일된 코드를 사용하여 성능을 향상시킵니다.

 

왜 두 번이나 컴파일하나요?

이는 플랫폼 독립성 제공 방법하기 위해서다.

JVM은 바이트코드를 해석하고 실행하는 가상 머신입니다. 실행할 때, JVM은 클래스 파일에 포함된 바이트코드를 메모리에 로드하고 해석하여 해당 플랫폼에 맞는 기계어로 변환하여 실행합니다. 즉, 개발자는 자신의 코드를 한 번 작성하고 컴파일하면, 해당 코드는 JVM이 설치된 모든 운영 체제에서 실행될 수 있습니다.

 

 

* 구문트리

소스 코드의 구조를 트리 형태로 표현한 것입니다. 이는 컴파일러나 인터프리터가 소스 코드를 분석하고 이해하는 데 사용됩니다. 구문 트리는 소스 코드의 구문적 구성 요소(토큰)와 그들 간의 계층적 관계를 나타냅니다.

반응형