본문 바로가기

Develop/JAVA

[JAVA] 자바 파일(.java)이 컴파일 되는 과정에 대한 설명

자바가 컴파일 되는 과정

1) 자바 소스파일(.java)을 작성한다.

2) 자바 컴파일러(javac)를 통해 해당 소스를 컴파일한다.

이때 .class 파일이 생성되며, 바이트코드의 각 명령어는 Opcode와 추가 피연산자로 이루어진다.

3) 컴파일된 바이트코드를 JVM의 클래스 로더에게 전달한다.

자바는 컴파일타임이 아니라 런타임에 클래스를 처음으로 참조할 때 해당 클래스를 로드하고 링크하는 특징이 있다. 이러한 동적 로드를 담당하는 부분이 JVM의 클래스 로더이다. 클래스 로더는 아래 그림과 같은 과정을 거쳐 클래스를 로드하고 링크하고 초기화한다.

클래스 로드 단계

  • Loading: 클래스 파일을 읽어 JVM 메모리에 로드한다. (힙영역, Metaspace) 이때 로드된 클래스와 부모 클래스의 FQN(Fully Qualified Name), 클래스/인터페이스/Enum 관련 여부, 변수/메서드 정보를 올리게 된다. 클래스 파일을 로드한 후 JVM은 힙 메모리에 해당 클래스 객체를 생성한다. Java.lang.Object.getClass() 메서드를 통해 해당 객체 참조를 얻을 수 있다.
  • Verifying: 읽어 들인 클래스가 자바 언어 명세/JVM 명세에 부합하는지 검사한다. 만약 검증이 실패하면 런타임 에러가 발생한다. (java.lang.VerifyError) 클래스 로드의 전 과정 중에서 가장 많은 시간이 소요된다.
  • Preparing: 클래스가 필요로 하는 메모리를 할당하고, 클래스에서 정의된 필드/메서드/인터페이스를 나타내는 메모리 구조를 준비한다. (해당 메모리를 기본값으로 초기화한다는 의미로 받아들였다)
  • Resolving: 클래스의 상수 풀 내 모든 심볼릭 레퍼런스를 다이렉트 레퍼런스로 변경한다. 이때 메서드 영역을 탐색한다.
  • Initializing: 클래스 변수들을 적절한 값으로 초기화한다. (static 필드들을 설정한 값으로 초기화)
자바 1.8 이후 JVM 내의 PermGen 메모리 영역이 사라지고, Metaspace 메모리 영역이 생겼다.

4) 3번 과정을 통해 JVM 내 런타임 데이터 영역에 배치된 바이트코드가 Execution Engine에 의해 실행된다.

실행 엔진은 자바 바이트 코드를 명령어 단위로 읽어서 기계어로 변환하여 실행한다. 앞서 설명한 것처럼 바이트코드는 Opcode(1바이트)와 추가 피연산자로 이루어져 있다. 따라서 실행 엔진은 하나의 Opcode를 가져와서 피연산자와 함께 작업을 수행한 다음, 다음 OpCode를 수행하는 식으로 동작한다.

 

실행 엔진이 동작하는 방식은 두 가지가 있다.

  • Interpreter 방식
    • 바이트 코드를 명령어 단위로 읽어서 한 줄씩 실행한다.
    • 인터프리터 언어의 단점을 그대로 가지고 있다. (속도가 느리다)
  • JIT(Just-In-Time) 방식
    • Interpreter 방식의 단점을 보완하기 위해서 도입되었다.
    • 인터프리터 방식으로 실행하다가 적절한 시점에 바이트 코드 전체를 컴파일해서 네이티브 코드로 변경하여 실행한다.
    • 이때 네이티브 코드는 캐시에 보관하기 때문에 한 번 컴파일된 코드는 빠르게 실행된다.
    • JVM은 내부적으로 해당 메서드가 얼마나 실행되는지 체크하고 일정 수준을 넘어설 때 JIT 컴파일을 수행한다.