자바 컴파일 프로세스

유경우·2024년 2월 5일
0

CS

목록 보기
1/21
post-custom-banner

1. 들어가기 전

Java 프로그래밍 언어는 "한 번 작성하면 어디서나 실행"이라는 원칙을 자랑합니다.
사용자가 어느 장치를 사용하더라도, 모든 장치에서 실행할 수 있도록 응용 프로그램을 만들 수 있습니다.
이런 방식이 어떻게 가능한 것일까요?

2. Complie

자바의 컴파일 과정을 알기 전에 컴파일이라는 용어를 알아봅시다.

Compile은 사전적 의미로는 해석, 번역 등의 뜻을 가지고 있지만, 프로그래밍 용어로 쓰일 때는 소스 코드를 기계어, 바이트 코드 등의 언어로 변환하는 과정을 말합니다.

쉽게 말해서, 소스 코드는 일반적으로 사람이 읽을 수 있는 고급 언어로 작성됩니다(Java, C++ 등). 컴파일러는 이것을 컴퓨터가 이해할 수 있는 언어로 바꿔줍니다.

3. Java Compile Process

컴파일의 뜻을 알았으니, 이제 자바 컴파일 과정을 살펴봅시다.
자바 언어로 작성된 소스 코드가 기계어로 바뀌는 과정입니다.
이 과정은 어떻게 이루어질까요?

Java -> 컴파일링 -> 바이트 코드 -> JVM -> 기계어

이러한 과정을 거쳐 소스 코드가 기계어로 번역됩니다.

  1. 개발자가 자바 소스코드(.java)를 작성합니다.

  2. 자바 컴파일러는 자바 소스코드파일을 읽어 javac의 명령에 따라 바이트 코드(.class)로 컴파일합니다. 바이트 코드 파일은 아직 컴퓨터가 읽을 수 없는 코드입니다.

  3. 컴파일된 바이트 코드는 JVM(Java Virtual Machine)의 클래스 로더(Class Loader)에게 전달되고 동적 로딩(Dynamic Loading)을 통해 런타임 환경에서 필요한 클래스들을 로드합니다.

    3-1. 클래스 로더의 세부 동작
    1)로드: 클래스 파일을 가져와서 JVM의 메모리에 로드합니다.
    2)검증: 자바 언어 명세서(Java Language Specification) 및 JVM 명세에 명시된 대로 구성되어 있는지 확인합니다.
    3)준비: 클래스가 필요로 하는 메모리를 할당합니다.(필드, 메서드, 인터페이스 등)
    4)분석: 클래스의 상수 풀 내 모든 심볼릭 레퍼런스를 다이렉트 래퍼런스로 변경합니다.
    5)초기화: 클래스 변수들을 적절한 값으로 초기화 합니다.(static 필드)

  4. 실행 엔진(Execution Engine)은 JVM 메모리에 올라온 바이트 코드들을 명령어 단위로 하나씩 가져와서 실행합니다. 이때 실행 엔진에서 바이트 코드로 변환하는 방식에는 두 가지가 있습니다.

    4-1. 인터프리터

    • 바이트 코드 명령어를 하나씩 읽고 해석하여 실행합니다.
    • 하나하나의 실행은 빠르지만, 전체적인 실행 속도는 상대적으로 느립니다.

    4-2. JIT 컴파일러 (Just-In-Time Complier)

    • 인터프리터의 단점을 보완하기 위해 도입된 방식입니다.
    • 바이트 코드 전체를 컴파일하여 바이너리 코드로 변경하고, 이후에는 메서드를 더 이상 인터프리팅하지 않고, 바이너리 코드로 직접 실행하는 방식입니다.
    • 또한 JIT Complier에 의해 해석된 코드는 캐시에 보관하기 때문에 한 번 컴파일된 후에는 빠르게 수행됩니다.
    • 그러나 인터프리팅 방식보다는 훨씬 오래 걸리므로, 한번만 실행되는 코드는 인터프리팅하는 것이 유리합니다.

4. Compiler vs Interpreter

위의 실행 엔진에서 살짝 설명했던 컴파일러와 인터프리터의 차이를 더 자세히 알아보겠습니다.

4-1. 컴파일러

컴파일러는 프로그램 전체를 스캔하여 이를 모두 기계어로 번역합니다.
전체를 스캔하기 때문에 대개 컴파일러의 초기 스캔 시간은 오래 걸립니다.
하지만 컴파일러는 초기 스캔을 마치고 나서 다음에 실행할 때 이전에 만들어 놓았던 실행파일을 쓰기 때문에 인터프리터보다 전체 시간을 놓고보면 더 빠릅니다.
컴파일러는 고급 언어로 작성된 소스 코드를 기계어로 번역하고 이 과정에서 오브젝트 코드(Object Code)라는 파일을 만드는데 이 오브젝트 코드를 묶어서 하나의 실행 파일로 다시 만드는 링킹(Linking)이라는 작업해야 합니다.
이 때문에 컴파일러는 보통 인터프리터 보다 많은 메모리를 사용합니다.
그리고 컴파일러는 전체를 스캔하여 하는 방식 때문에 실행 전에 오류를 발견 할 수 있습니다.
대표적인 언어로 C,C++,JAVA 등이 있습니다.

4-2. 인터프리터

인터프리터는 반대로 한번에 한문장씩 번역합니다.
그렇기 때문에 한번에 전체를 스캔하고 실행파일을 만들어서 실행하는 컴파일러보다 실행시간이 더 걸립니다.
하지만 인터프리터는 컴파일러 처럼 목적코드를 만들지도 않고, 링킹 과정도 거치지 않기 때문에 메모리 효율이 더 좋습니다.
인터프리터는 한문장씩 번역하기 때문에 프로그램을 실행시키고 한 문장씩 번역될 때 오류를 만나게 되면 바로 프로그램을 중지합니다.
대표적인 언어로 Python, Ruby, Javascript 등이 있습니다

5. JVM

자바 가상 머신 JVM은 자바 프로그램 실행환경을 만들어주는 소프트웨어입니다.
자바 코드를 컴파일하여 바이트 코드로 만들면 이 코드가 자바 가상 머신 환경에서 실행됩니다.
JVM은 자바 실행 환경 JRE(Java Runtime Environment)에 포함되어 있습니다.

5-1. JVM에서의 실행 과정
1. Class Loader를 통해 .class 파일들을 JVM에 올립니다.
2. JVM에 있는 .class 파일들을 Execution Engine의 Interpreter와 JIT Complier를 통해 해석됩니다.
3. 해석된 바이트 코드는 Runtime Data Area에 배치되어 실질적인 수행이 이루어집니다.

6. Class Loader

자바는 동적으로 클래스를 읽어오므로, 프로그램이 실행 중인 런타임에서야 모든 코드가 자바 가상 머신과 연결됩니다.
이렇게 동적으로 클래스를 로딩해주는 역할을 하는 것이 바로 클래스 로더(Class Loader)입니다.
자바에서 소스를 작성하면 .java파일이 생성되고, 이 .java소스를 컴파일러가 컴파일하면 .class파일이 생성됩니다. 클래스 로더는 이 .class 파일을 묶어서 JVM이 운영체제로부터 할당받은 메모리 영역인 Runtime Data Area로 적재합니다.

7. Runtime Data Area

런타임 데이터 영역은 JVM의 메모리 영역으로 자바 애플리케이션을 실행할 때 사용되는 데이터들을 적재하는 영역입니다.

  1. 메소드 영역 (Method Area): 클래스 정보(클래스 이름, 부모 클래스 이름, 메소드, 변수), static 변수, final 클래스(상수) 등을 저장합니다. JVM에 의해 공유되는 영역입니다.
  2. 힙 영역 (Heap Area): 모든 객체와 배열이 이 영역에 할당됩니다. Heap 영역은 JVM이 시작될 때 생성되며, 가비지 컬렉터에 의해 관리됩니다.
  3. 스택 영역 (Stack Area): 지역 변수, 메소드 실행 정보, 메소드 반환값 등을 저장합니다. 각 쓰레드마다 하나씩 생성되며, 메소드 호출 시마다 각각의 스택 프레임이 생성됩니다.
  4. PC 레지스터 (PC Registers): 현재 실행중인 JVM 명령의 주소를 저장합니다. 이 레지스터는 각 쓰레드마다 하나씩 존재합니다.
  5. 네이티브 메소드 스택 (Native Method Stack): 자바 외의 언어로 작성된 네이티브 메소드에 대한 정보를 저장합니다.
    이러한 각 영역은 서로 다른 용도로 사용되며, 자바 애플리케이션의 실행 상태를 결정하는 중요한 역할을 합니다.

8. Garbage Collector(GC)

자바 가상 머신은 가비지 컬렉터(garbage collector)를 이용하여 더는 사용하지 않는 메모리를 자동으로 회수해 줍니다.
따라서 개발자가 따로 메모리를 관리하지 않아도 되므로, 더욱 손쉽게 프로그래밍을 할 수 있도록 도와줍니다.
Heap 메모리 영역에 생성(적재)된 객체들 중에 참조되지 않은 객체들을 탐색 후 제거하는 역할을 하며 해당 역할을 하는 시간은 정확히 언제인지를 알 수 없습니다.
GC역할을 수행하는 스레드를 제외한 나머지 모든 스레드들은 일시정지상태가 됩니다.

결과

자바의 "한 번 작성하면 어디서나 실행"이라는 원칙은 자바 컴파일러와 JVM의 동작 방식 덕분입니다.
자바 컴파일러는 자바 소스 코드를 플랫폼에 독립적인 바이트 코드로 컴파일하고, 이 바이트 코드는 JVM을 통해 각각의 플랫폼에 맞는 기계어로 변환되어 실행됩니다.
JVM은 플랫폼에 종속적이므로, 각 플랫폼에 맞는 JVM만 설치되어 있다면 동일한 자바 프로그램을 어디서든 실행할 수 있습니다.
또한, JVM은 메모리 관리와 가비지 컬렉션 등을 수행하여 개발자가 이러한 부분에 대해 신경 쓰지 않아도 되게 해줍니다. 이런 특성은 자바가 다양한 환경에서 무리 없이 실행될 수 있게 도와주며, 개발자가 보다 집중해서 비즈니스 로직을 구현할 수 있게 합니다.

출처:https://www.prepbytes.com/blog/java/java-compilation-process/
https://www.techtarget.com/whatis/definition/compiler
https://studynote.oopy.io/20c4cf66-f781-4299-95dd-52d60cca6a38
https://velog.io/@minseojo/Java-%EC%9E%90%EB%B0%94-%EC%BB%B4%ED%8C%8C%EC%9D%BC-%EA%B3%BC%EC%A0%95-JVM-%EB%82%B4%EB%B6%80-%EA%B5%AC%EC%A1%B0
https://yummy0102.tistory.com/510
https://velog.io/@jhur98/%EC%BB%B4%ED%8C%8C%EC%9D%BC%EB%9F%ACcompiler%EC%99%80-%EC%9D%B8%ED%84%B0%ED%94%84%EB%A6%AC%ED%84%B0interpreter%EC%9D%98-%EC%B0%A8%EC%9D%B4

profile
개발자
post-custom-banner

0개의 댓글