본문 바로가기
Programming Language 👅

[Java] 자바의 컴파일 과정

by 서니서닝 2023. 3. 27.
728x90

👶 자바의 탄생

자바는 애초에 일반 컴퓨터에 사용될 목적으로 설계된 것이 아니다.

1991년 선마이크로시스템즈(SUN Microsystems)는 제임스 고슬링(James Gosling)이란 엔지니어를 중심으로 그린 프로젝트(Green project)라는 이름으로 가전 제품의 적합한 소프트웨어 언어를 개발하기 시작했다.

 

기존의 언어로 작성된 프로그램들은 컴퓨터 플랫폼(platform) 간에 호환성이 없어, 플랫폼에 따라 소스를 다시 컴파일하거나 아예 프로그램을 다시 작성해야하는 단점이 존재했다.

 

이에 선마이크로시스템즈는 플랫폼 독립적(platform independent)이며 메모리 사용량이 적은 새로운 언어와 실행 체계를 발표했고, 처음에는 이 언어의 이름을 오크(oak)라 지었다.

 

이 언어는 인터넷과 웹이 엄청난 속도로 발전하면서 급속도로 퍼지게 되었다.

웹은 한 번 개발된 웹 콘텐츠(웹 프로그램)가 전 세계의 다양한 클라이언트 플랫폼에 설치된 웹 브라우저에서 수정 없이 실행되도록 하는 것인데, 이 언어의 플랫폼 독립성과 딱 들어맞았기 때문이다.

 

선마이크로시스템즈는 1995년, 마침내 자바(Java)라는 이름으로 발표하였다.

https://steady-snail.tistory.com/67

기존 언어의 플랫폼 종속성

자바 이전 프로그래밍 언어로 작성된 프로그램이 컴파일 되면 실행될 컴퓨터 플랫폼, 즉 CPU와 OS에 종속된 기계어 코드로 변환된다.

그러므로 프로그램은 실행하고자 하는 각 플랫폼을 대상으로 소스 코드를 수정하거나, 각 플랫폼에 따로 컴파일하여 플랫폼에 맞는 기계어 프로그램을 생성하여야 한다. 이러한 특징을 플랫폼 종속성(platform dependence)라고 칭한다.

 

자바의 플랫폼 독립성

그러나 자바는 애초에 플랫폼에 독립적으로 설계되었다.

따라서 한번 작성되고 컴파일된 자바 코드는 OS나 CPU 등 플랫폼에 관계없이 자바 가상기계(JVM, Java Virtual Machine)만 있으면 어떤 컴퓨터든 동일하게 실행된다.이를 WORA(Write Once Run Anywhere)라고 한다.

(참고로 JVM은 하드웨어와 OS 위에서 실행되기 때문에 JVM 자체는 플랫폼에 종속적 즉, 플랫폼에 따라 호환되는 JVM을 실행시켜줘야한다.)

 

 

🚀 자바 코드의 실행과정

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

  1. Java Source 파일을 Java Compiler를 통해 Java Byte Code로 컴파일 한다.
    • 컴파일된 파일의 확장자는 .class이다.
  2. 컴파일된 바이트 코드를 JVM의 Class Loader로 전달한다.
  3. Class Loader는 동적 로딩(Dynamic Loading)을 통해 필요한 클래스들을 로딩 및 링크하여 Runtime Data Area, 즉 JVM의 메모리에 올린다
  4. 실행엔진(Execution Engine)은 JVM 메모리에 올라온 바이트 코드들을 명령어 단위로 하나씩 가져와 실행한다.

 

🔥 JVM

Java Virtual Machine
자바 프로그래밍 언어로 작성된 소프트웨어를 실행하기 위한 가상머신이다.

https://steady-snail.tistory.com/67

위에서 언급했듯이, 자바 프로그램은 자바 컴파일러를 통해 바이트 코드로 변환된다.

이 바이트 코드는 플랫폼에 독립적이며, JVM이 설치된 모든 운영 체제에서 실행될 수 있다.

JVM은 이 바이트 코드를 읽어들여 메모리에 로드하고, 필요한 라이브러리를 포함하여 실행환경을 구성한다. 그리고 바이트 코드를 기계어로 변환하여 실행한다.

 

JVM은 자바 프로그램의 실행을 추상화하며, 메모리 관리, 스레드 관리, 예외 처리 등을 담당한다.

또한 JIT 컴파일러를 사용하여 실행 속도를 향상시키는 등 다양한 최적화 기능을 제공한다.

 

JVM은 크게 세 가지 구성 요소로 이루어져 있다.

 

1. 클래스 로더(Class Loader)

JVM이 실행할 클래스 파일을 로드하는 역할

자바 언어는 클래스 기반 객체지향 언어이기 때문에, 클래스 파일은 자바 프로그램에서 중요한 역할을 한다. Class Loader는 클래스 파일을 로드하고, 필요한 경우에는 동적으로 클래스를 생성하며, 메모리에 올린다.

 

클래스 로드는 세 단계로 이루어진다.

  1. 로딩(Loading):
    • 클래스 파일을 가져와서 JVM의 메모리에 로드
    • 클래스 파일은 보통 .class 확장자를 가지며, 이 파일에는 자바 클래스의 바이트 코드가 포함되어 있다.
    • 클래스 파일을 찾을 때, 클래스 패스(Class Path)를 참조한다.
  2. 연결(Linking):
    • 클래스 파일이 메모리에 적재될 때, JVM이 상수 풀(Constant Pool)을 포함하여 클래스의 레이아웃을 완성하는 과정
    1. 검증(Verification) :
      • 클래스 파일이 유효한 바이트 코드인지 확인
      • 바이트 코드의 구조와 상수 값 등을 검사하여, 실행 시에 예기치 않은 동작이 발생하지 않도록 보장한다.
      • 자바 언어 명세(Java Language Specification) 및 JVM 명세에 명시된 대로 구성되어 있는지 검사한다.
    2. 준비(Preparation) :
      • 클래스가 필요로 하는 메모리를 할당한다. (필드, 메서드, 인터페이스 등등)
    3. 분석(Resloving) :
      • 클래스의 상수 풀 내 모든 심볼릭 레퍼런스를 다이렉트 레퍼런스로 변경한다.
  3. 초기화 : 클래스 변수들을 적절한 값으로 초기화한다. (static 필드)

 

2. 런타임 데이터 영역(Runtime Data Area)

JVM이 클래스와 객체를 실행하는 데 필요한 데이터를 저장하는 메모리 공간

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

크게 PC Register, Stack, Native Method Stack, Method Area, Heap 으로 구성
이중 PC 레지스터(PC Register), JVM 스택(JVM Stack), 네이티브 메서드 스택(Native Method Stack)은 스레드마다 하나씩 생성되며 힙(Heap), 메서드 영역(Method Area), 런타임 상수 풀(Runtime Constant Pool)은 모든 스레드가 공유해서 사용한다.

 

  • PC Register :
    • 각 스레드마다 하나씩 존재
    • 스레드가 시작될 때 생성
    • 현재 실행 중인 JVM 명령의 주소를 저장

  • JVM Stack :
    • 각 스레드마다 하나씩 존재
    • 스레드가 시작될 때 생성
    • 메서드 호출 시마다 생성되는 스택 프레임(Stack Frame)이 저장되는 곳
    • JVM은 오직 JVM 스택에 스택 프레임을 추가하고(push) 제거하는(pop) 동작만 수행한다.
    • 예외 발생 시 printStackTrace() 등의 메서드로 보여주는 Stack Trace의 각 라인은 하나의 스택 프레임을 표현한다.
  • Native Method Stack :
    • 자바 외의 언어로 작성된 네이티브 코드를 위한 스택
    • JNI(Java Native Interface)를 통해 호출하는 C/C++ 등의 코드를 수행하기 위한 스택으로, 언어에 맞게 C 스택이나 C++ 스택이 생성된다.
  • Heap :
    • 인스턴스 또는 객체를 저장하는 공간,  가비지 컬렉션 대상
    • 동적으로 생성된 객체가 저장되는 곳
  • Method Area :
    • 모든 스레드가 공유하는 영역
    • JVM이 시작될 때 생성
    • 클래스 정보, 상수, static 변수 등이 저장되는 곳

 

3. 실행 엔진(Execution Engine)

JVM이 바이트 코드를 실행하는 역할
바이트 코드를 읽어들여 명령어 단위로 해석하고, 필요한 경우에는 JIT 컴파일러를 사용하여 기계어로 변환하여 실행한다.

자바 바이트 코드는 기계가 바로 수행할 수 있는 언어보다는 비교적 인간이 보기 편하게 기술되어 있다.

그래서 실행 엔진은 이런 바이트 코드를 실제 JVM 내부에서 기계가 실행할 수 있는 형태로 변경한다. 그 방식에는 두가지가 있다.

  • 인터프리터 :
    • 바이트 코드 명령어를 하나씩 읽어서 해석하고 실행한다.
    • 하나하나의 실행은 빠르나, 전체적인 실행 속도가 느리다는 단점을 가진다.
  • JIT 컴파일러(Just-In-Time Compiler) :
    • 인터프리터의 단점을 보완하기 위해 도입된 방식
    • 바이트 코드 전체를 컴파일하여 바이너리 코드로 변경하고 이후에는 해당 메서드를 더이상 인터프리팅 하지 않고, 바이너리 코드로 직접 실행하는 방식
    • 하나씩 인터프리팅하여 실행하는 것이 아니라 바이트 코드 전체가 컴파일된 바이너리 코드를 실행하는 것이기 때문에 전체적인 실행속도는 인터프리팅 방식보다 빠르다.
    • 따라서, JIT 컴파일러를 사용하는 JVM들은 내부적으로 해당 메서드가 얼마나 자주 수행되는지 체크하고, 일정 정도를 넘을 때에만 컴파일을 수행한다.
  1.  

 

 

📖 reference

명품 자바 에센셜

JVM Internal

[Java] 컴파일 과정

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

728x90

'Programming Language 👅' 카테고리의 다른 글

[Java] 자바는 Call by Value!  (0) 2023.05.19
[Java] 제네릭(Generic)  (0) 2023.04.25
[Java] Stream API  (0) 2023.04.10
[Java] 상속과 오버라이딩(inheritance & overriding)  (0) 2023.02.28

댓글