도대체 .java 확장자 파일은 어떻게 우리가 원하는 대로 실행될 수 있을까요?

이것에 대한 궁금증을 갖고 Java의 컴파일 과정에 대해서 공부하게 되었습니다.

 

틀린 부분이 있다면 지체없이 댓글을 남겨주시면 최대한 빠르게 수정하도록 하겠습니다!!

 

목차


    0. 전체 실행 과정

    아래 사진은 전반적인 실행 과정을 간략하게 표시해놓은 그림입니다.

    굉장히 복잡한 단어들이 나열되어있는 것 같지만, 하나하나 뜯어보면 처음 자바를 익히는 사람들도 충분히 이해할 수 있는 구조로 되어있습니다.

     

    이제 하나하나 뜯어가며 살펴보도록 하죠!

     

     


    1. JVM이란?

    Java 이전 C와 C++의 경우 컴파일 플랫폼과 타겟 플랫폼이 다른 경우 프로그램이 동작하지 않는 경우가 있었습니다.

    (이유는 운영체제, CPU 하드웨어 등 다양...)

     

    출처 : 무민의 JVM Stack & Heap 영상

     

    이런 상황에서 타겟 플랫폼에 맞춰서 컴파일하는 것크로스컴파일이라고 부릅니다.

     

    출처 : 무민의 JVM Stack & Heap 영상

     

    이렇게 복잡하게 타겟 플랫폼에 맞춰서 컴파일해야한다는 단점을 보완하기 위해서, Java의 바이트코드는 JVM 위에서 돌아감으로써 문제를 어느정도 해결할 수 있었습니다.

     

    JVM이 설치된 플랫폼이라면 어디든 똑같이 바이트코드가 동작할 수 있는 것이죠!

    (물론 JVM은 각 타겟 플랫폼에서 동작할 수 있는 버전이 설치되어있어야 합니다)

     

    출처 : 무민의 JVM Stack & Heap 영상

     

    그렇다면 Java가 크로스컴파일이 아닌 JVM방식을 선택한 이유는 무엇일까요?

     

    Java가 등장하던 시기는 새로운 운영체제 및 디바이스가 마구마구 출시되기 시작하던 시절이었고, 모든 운영체제 및 기종들에 대해서 컴파일을 고려하는 것은 굉장히 어려운 일이었습니다.

    단순히 JVM위에서 돌아갈 수 있게 해놓고, 각 운영체제 및 디바이스별로 JVM만 설치할 수 있다면 조금 더 편하게 모든 곳에서 동일하게 동작할 수 있는 시스템을 구축할 수 있을 것이라고 생각했기에 이런 방식을 선택했다고 보시면 되겠습니다!

    (자바의 야심이라고 볼 수 있겠네요!)


    2. 바이트코드 변환

    이제 JVM에 대해서도 어느 정도 이해했으니, 맨 윗 부분에 있던 자바 컴파일러에 의한 바이트코드 변환부터 차근차근 살펴봅시다.

     

    개발자들이 .java 파일을 생성하고 Build를 하게 되면,

    자바 컴파일러의 javac라는 명령어를 사용하여 자바 바이트코드로 구성된 .class파일을 생성하게 됩니다.

     

    해당 클래스 파일을 통해서 어떤 환경에서든 같은 코드 결과를 얻을 수 있게 된겁니다!

    (이 부분은 JVM의 클래스 로더를 설명하는 과정에서 다시 한 번 이해할 수 있습니다!!)

     

     

    아래 첨부한 사진을 보시면, 실제로 .java파일을 javac를 이용하여 빌드한 후에 추출된 .class파일을 확인해보았습니다.

    (왼쪽이 .java 파일, 오른쪽이 .class 바이트코드입니다)

    ICONST는 이미 저장되어있는 정수변수값을 가져올 때 사용하고, ISTORE는 사용하는 메모리단을 보여줍니다.

    또한 IMUL과 ISUB는 각각 곱셈과 뺄셈을 담당한다는 것도 어렴풋이 알 수 있습니다.

     

    이렇게 우리가 어느 정도 알아들을 수 있게 컴파일됐다는 것은, .class파일이 완벽한 기계어가 아니라는 것의 한 가지 증거가 됩니다!

     

     

    추가로, 바이트코드는 JVM과 같은 가상머신이 이해할 수 있는 언어이고, 바이너리 코드는 CPU가 이해할 수 있는 언어입니다.

    자바의 경우 자바 컴파일러가 JVM이 이해할 수 있도록 소스 파일을 .class 파일로 변환하는 것이고, C의 경우 한번에 바이너리코드로 변환하는 것이죠.

     

    여기까지 이해했다면, JVM이 바이트코드를 읽어와야 한다는 필요성을 느낄 수 있을 것입니다!

     


    3. 클래스 로더와 실행 엔진

    이렇게 추출해낸 .class파일은 클래스 로더에 의해서 JVM의 영역으로 들어가게 됩니다.

     

     

    클래스 로더가 불러오는 과정을 간략하게 설명하면, 로딩 과정에서 메서드 영역에 클래스를 저장하고, 링크 과정에서 구성 요소를 검증하고 메모리를 할당한 후에, 초기화 과정에서 클래스 변수들을 적절한 값으로 초기화합니다.

     

    또한 클래스 로더는 하나만 존재하는 것이 아니라 계층적으로 존재해서, 상위 클래스 로더가 가져온 내용을 먼저 확인하고 찾지 못한 경우 그때 하위 클래스 로더가 클래스를 로드하게 됩니다.

     

    하단 그림이 바로 클래스 로더 위임 모델인데, Bootstrap Class Loader이 JVM을 기동할 때 생성되는 가장 기본적인 자바 API를 로드하는 클래스로더입니다.

    Extension Class Loader는 기본 자바 API를 제외한 나머지 확장 클래스를 로드하고, System Class Loader는 애플리케이션의 클래스들을 로드합니다.

    그 하위의 User-Defined Class Loader는 사용자가 직접 생성해서 사용하는 클래스 로더입니다.

     

     

     

    이렇게 어찌어찌 JVM에 들어온 바이트코드는 실행 엔진에 의해 기계어로 해석되어 메모리에 배치되고 실행됩니다.

     

    여기서 인터프리터 방식JIT 컴파일러 방식으로 나눠집니다.

    인터프리터 방식의 경우 바이트 코드를 한 줄씩 읽고, JIT 컴파일러 방식은 바이트 코드를 한번에 컴파일하고 캐싱하는 방식입니다.

     

    JVM은 기본적으로 인터프리터 방식을 사용하고, 내부적으로 특정 메서드가 얼마나 자주 수행되는지 체크하고 일정 정도를 넘을 때만 JIT 컴파일러 방식을 사용합니다.

     

    다만 실행 엔진이 어떻게 동작하는 지는 JVM 명세에 규정되어있지 않기 때문에 JVM을 만들어내는 곳에서는 다양한 방식으로 실행 엔진을 최적화하고 있다고 합니다.

     


    4. 런타임 데이터 영역

    이제 자바 컴파일 과정을 나타낸 그림의 최하단에 위치해있던 런타임 데이터 영역에 대해서 조금 더 자세하게 알아보겠습니다.

     

     

    클래스 로더의 로딩 과정을 통해 불러온 클래스는 메서드 영역에 저장됩니다.

    메서드 영역에는 클래스와 인터페이스에 대한 필드 및 메서드 정보, Static 변수, 메서드의 바이트코드 등등이 저장됩니다.

    메서드 영역은 JVM을 어떻게 만드냐에 따라 다양한 형태로 구현할 수 있습니다.

    특히 이 메서드 영역 안에는 런타임 상수 풀(Runtime Constant Pool)이라는 영역도 존재하는데, 각 클래스와 인터페이스의 상수, 그리고 메서드의 모든 정보를 담고 있는 테이블을 의미합니다.

    JVM은 런타임 상수 풀에서 특정 메서드나 필드의 실제 메모리상의 주소를 찾아서 참조하게 됩니다.

     

    인스턴스나 객체를 생성하는 경우에는 힙 영역에 저장됩니다.

    TMI 하나 방출하자면, 실행엔진의 가비지컬렉터는 보통 힙 영역의 메모리가 부족할 때 작동합니다.

     

     

    메서드 영역과 힙 영역은 모든 스레드가 공유한다는 특징을 가지고 있습니다.

     

    스택 영역의 경우 메서드가 수행될 때마다 하나의 스택 프레임이 생성되는 방식입니다.

    당연히 메서드가 종료되면 스택 프레임이 제거되고, 컴파일 시에 스택의 크기가 결정되는 특징을 갖고 있습니다.

     

    PC 레지스터 영역의 경우 현재 스레드가 수행중인 JVM 명령의 주소값이 담깁니다.

    멀티스레드 프로그래밍 환경에서 한 스레드가 작업하다가 다른 스레드로 잠시 CPU 점유를 넘겨주고 돌아왔을 때 어떤 일을 하고 있었는지 기억해야 거기서부터 다시 할 수 있겠죠?

    이럴 때 사용되는 영역입니다.

     

    네이티브 메서드 스택의 경우에는 Java의 바이트코드가 아닌 다른 언어(C, C++ 등..)로 작성된 메서드를 담아두는 공간입니다.

     

    스택 영역, PC 레지스터 영역, 네이티브 메서드 스택은 하나의 스레드가 생성될 때마다 같이 생성이 되고, 서로 다른 스레드가 침범할 수 없습니다.

     


    5. 정리

    지금까지 Java의 컴파일 과정이 어떻게 진행되는지에 대해서 알아본 내용을 간단하게 정리해봅시다.

     

    먼저 JVM을 사용하는 이유에 대해서 알아봤습니다.

    Java는 어디에서나 같은 결과물을 내기 위해서 JVM을 사용합니다.

     

    그 다음에는 Java 컴파일 과정에 대해 하나하나 살펴보았습니다.

    먼저 개발자가 만든 .java 파일을 자바 컴파일러의 javac에 의해 JVM이 읽을 수 있는 바이트코드로 바꿉니다.

    이 바이트코드는 계층적으로 존재하는 JVM의 클래스 로더에 의해 JVM의 메모리에 할당되고 실행 엔진에 의해 기계어로 변환되고, 최종적으로 실행됩니다.

    실행 엔진은 크게 두 가지로 나눌 수 있는데, 한줄씩 읽는 인터프리터 방식과 한번에 읽는 JIT 컴파일러 방식이 있습니다.

    또한 런타임 데이터 영역에서 읽어들인 데이터를 저장하게 되는데, 메서드 영역과 힙 영역의 경우 모든 스레드가 공유하고, 스택 영역과 PC 레지스터 영역, 네이티브 메서드 영역은 하나의 스레드가 각각의 영역을 보유합니다.

     

     

    엄청나게 어려워보였던 Java의 컴파일 과정이 어느 정도 요약이 되었을까요?!

    가비지컬렉터가 동작하는 방식, 생성된 바이트 코드가 의미하는 것 등등 핵심 내용에서 살짝 벗어나기 때문에 다루지 않은 내용들도 존재하지만, Java의 컴파일 과정에 대해 궁금증을 품었던 것처럼 하나하나 찾아보면서 익히면 Java에 대한 이해도가 훨씬 높아질 수 있을 것이라고 확신합니다!! 

    (저도 해당 포스팅을 작성하면서 Java의 동작 방식에 대해 더 깊은 정보까지 공부해보고 싶어졌습니다 ㅎㅎ)

     

    다음에는 더 자세하고 깊이있고 유익한 포스팅으로 돌아오겠습니다 :)

     


    6. 참고자료

     

    [JAVA] JVM 동작원리 및 기본개념

    JAVA라는 언어를 통해 코딩을 하고 있는 사람으로서 JAVA의 간단한 탄생배경 그리고 JAVA의 시작과 끝이라고 할 수 있는 JVM을 한 번 짚고넘어가려고 해요 우선 JAVA의 탄생배경을 좀 알고가면 이해하

    steady-snail.tistory.com

     

    알기쉽게 정리한 JAVA의 컴파일과정 및 JVM 메모리 구조, JVM GC

    알기쉽게 정리한 JAVA의 컴파일과정 및 JVM 메모리 구조, JVM GC 자바 개발자들이 간과 하기 쉬운 JAVA의 메모리 구조에 대해 포스팅 해보려고 합니다. 이와 관련하여 JAVA의 컴파일 과정과 Garbage Collect

    aljjabaegi.tistory.com

     

    [Java] 자바의 동작과정 Java Compiler와 JVM

    이클립스에서 *.java를 단축키 눌러서 실행시킬 줄만 알았지 *.java 파일이 어떠한 과정을 거쳐 실행이 되는지는 알지 못했습니다. 따라서 자바 컴파일러는 어떤 역할을 수행하며, 어디서 많이 들

    kingofbackend.tistory.com

     

    [Java] JVM의 클래스 로더란?

    java-study에서 스터디를 진행하고 있습니다. 클래스 로더란? 자바는 동적 로드, 즉 컴파일 타임이 아니라 런타임(바이트 코드를 실행할 때)에 클래스 로드하고 링크하는 특징이 있다. 이 동적 로드

    steady-coding.tistory.com

     

    The Java® Virtual Machine Specification

    Tim Lindholm Frank Yellin Gilad Bracha Alex Buckley

    docs.oracle.com

     

    NAVER D2 JVM Internal

    https://d2.naver.com/helloworld/1230

     

     

    반응형
    • 네이버 블로그 공유하기
    • 네이버 밴드에 공유하기
    • 페이스북 공유하기
    • 카카오스토리 공유하기