본문 바로가기
카테고리 없음

자바의 컴파일과 클래스 로딩 과정

by 데브겸 2024. 9. 16.

간만에 자바 자체를 공부해보는 시간. 예전에 공부했지만 남겨놓은 기록이 없어 다시 공부하는데 힘들었다. 이번 기회에 살짝 정리해두려고 한다.

 

JVM과 자바 바이트 코드

C나 C++은 컴파일 플랫폼과 타겟 플랫폼이 다를 경우, 프로그램이 동작하지 않음(환경, 플랫폼 = 운영체제 + CPU 아키텍쳐) 타겟 플랫폼에 맞춰 컴파일 하는 크로스 플랫폼 전략을 취했지만, Java 진형은 타겟 플랫폼에 상관없이 JVM 위에서 동작하도록 바꿨음. 디바이스마다 운영체제나 하드웨어가 다르기 때문에 이런 전략을 취함. 클래스 파일만 네트워크로 전달해주면 JVM 위에서 돌아가도록. (그리고 컴파일 결과물의 크기가 소스코드의 크기와 크게 다르지 않아 네트워크로 전송하여 실행하기도 쉬움)

 

자바는 WORA를 구현하기 위해 자바와 기계어의 중간 언어인 자바 바이트코드를 사용. 이 자바 바이트코드가 자바 코드를 배포하는 가장 작은 단위라고 볼 수 있다.

 

자바 바이트코드는 아래와 같이 16진수로 표현할 수 있다. 컴파일 결과물(.class 파일)을 hex editor로 보면 아래와 같이 나온다.

 

자바 바이트코드는 1바이트인 OpCode(명령어)와 2바이트인 Operand(피연산자)로 분리 가능함. .java 파일을 .class 파일로 컴파일한 것을 javap -c 명령어로 역어셈블하면 OpCode와 Operand 코드를 확인하기 쉬움

 

 

자세한 OpCode는 아래 위키피디아에서 확인 가능하다

List of Java bytecode instructions

 

List of Java bytecode instructions - Wikipedia

From Wikipedia, the free encyclopedia This is a list of the instructions that make up the Java bytecode, an abstract machine language that is ultimately executed by the Java virtual machine.[1] The Java bytecode is generated from languages running on the J

en.wikipedia.org

 

아래 OpCode에서 맨 앞에 숫자는 바이트 번호. alode_0는 0x2a, invokespecial은 0xb7 등으로 표현할 수 있다

0: aload_0  
1: invokespecial #1

 

 

컴파일 과정 상세

자바의 소스 코드도 C와 마찬가지로 크게는 전처리(매크로 인라인화, 주석제거) → 컴파일(어셈블리어 코드로 변환) → 어셈블리(어셈블리어 코드를 기계어로 변환하는 과정) → 링크(링커가 기계어 코드와 공유 라이브러리 등 다른 코드를 합쳐 최종 실행 파일 생성)과정을 거친다고 볼 수 있다.

 

  1. Lexical Analysis (어휘 분석): 소스 코드에서 문자 단위로 읽어 어휘소(lexeme)를 식별하고 어휘소를 설명하는 토큰 스트림(Token Stream)을 생성한다. 이때 토큰은 언어에서 최소한의 의미 식별 단위로 볼 수 있는데 public, class, main과 같은 키워드(keyword)나, 변수이름, 상수이름 같은 식별자(identifier)을 나타낸다.*이때 식별자(identifier) 토큰은 어휘 분석 단계에서 심볼 테이블에 저장되고 이후 단계에서 계속 사용된다
  2. Syntax Analysis (구문 분석): 구문 분석기가 토큰 스트림의 문법을 검사하고(안 맞으면 컴파일 에러) 파스 트리(Parse Tree)를 생성한다.
  3. Symantic Analysis (의미 분석): 타입 검사, 자동 타입 변환 등이 진행된다
  4. Intermediate Code Generation (중간 코드 생성): 파스 트리를 바탕으로 기계어로 변환하기 좋은 형태의 중간언어로 된 중간 코드를 생성한다. 자바 바이트코드도 이 중간 코드에 해당된다.이때 상수풀과 런타임 상수풀에 대해서 헷갈릴 수 있는데, 상수풀은 컴파일된 .class 파일의 일부로 .class 파일 내에서 ‘constant_pool’ 테이블로 존재한다. 반면 런타임 상수풀은 JVM 메모리 내에 존재하면서 심볼릭 레퍼런스 정보를 알려주는데 사용된다. 심볼릭한 내용만 알고 있으므로, 실제 클래스의 레퍼런스 관계를 알아내기 위해선 필요한 클래스를 실제로 로드해서 상수풀 내용을 참고해야 한다. 이 과정을 Constant Pool Resolution이라고 한다 어휘 분석 단계에서 식별자 토큰을 바탕으로 만들어진 심볼 테이블이 중간 코드 생성 단계에서 클래스나 인터페이스별 상수 풀을 구성하는데 사용된다. 이 상수 풀에 저장된 정보는 해당 클래스나 인터페이스가 실제로 생성될 때 런타임 상수 풀을 구성하는데 사용된다.
  5. Code Optimization (중간 코드 최적화): 중간 코드가 더 효율적인 기계어로 변환되도록 최적화하는 과정이 진행된다.

 

클래스 로딩 과정

만들어진 모든 자바 바이트 코드를 한 번에 JVM 위에 올려 사용하지는 않는다. 최상위 클래스인 Object와 그밖에 필요한 java.lang, java.util 등 일부 자바 API 패키지 정도만 로드한다. 그 외에는 런타임에 클래스가 처음 참조할 때 해당 클래스를 동적으로 로드해서 사용하게 된다.

 

필요한 클래스를 로딩하는 과정을 Class Loader SubSystem을 통해 진행시키는데, 크게 1) Loading, 2) Linking, 3) Initialization 과정으로 나눌 수 있다.

 

Loading

.class 파일을 로딩하는 과정이다. 클래스 로더가 요청을 받으면 애플리케이션 클래스 로더 → 플랫폼 클래스 로더 → 부트스트랩 클래스 로더 순서로 각각 해당 클래스를 로드한 적이 있는지 확인한다. 없으면 다시 부트스트랩 클래스 로더 → 플랫폼 클래스 로더 → 애플리케이션 클래스 로더 순서로 자신이 담당하는 path에서 해당 클래스가 있는지 확인한다. 만약 끝까지 없다면 ClassNotFoundException이 발생한다.

  • 부스트트랩 클래스 로더: JAVA_HOME\lib에 있는 코어 자바 API를 제공한다. 최상위 우선순위를 가진 클래스 로더
  • 플랫폼 클래스 로더((구) 확장 클래스 로더): JAVA_HOME\lib\ext 폴더 또는 java.ext.dirs 시스템 변수에 해당하는 위치에 있는 클래스를 읽는다.
  • 애플리케이션 클래스 로더(or 시스템 클래스 로더): 애플리케이션 클래스패스(애플리케이션 실행할 때 주는 -classpath 옵션 또는 java.class.path 환경 변수의 값에 해당하는 위치)에서 클래스를 읽는다.

 

Linking

크게 검증(Verify), 준비(Prepare), 해석(Resolution) 단계로 나뉘어진다.

  • 검증: .class 파일의 코드가 잘 작성되었는지, JVM 규격에 따라 검증된 컴파일러에서 생성되었는지 등을 검사한다. 바이트코드 검증기(Bytecode verifier)가 내부적으로 이 과정을 담당한다. 만약 이 단계에서 실패하면 런타임 에러(java.lang.VerifyError)가 발생한다
  • 준비: 클래스가 필요로 하는 메모리를 할당(메모리 부족하면 OOM 발생함), 정적 필드가 만들어지고 기본값으로 초기화됨
  • 해석: 런타임 상수풀에 심볼릭 참조들이 로드한 클래스 파일 내 상수 풀을 바탕으로 직접 참조로 대체되는 과정

 

Initialization

static 값 초기화 및 변수에 할당, 정적 변수 초기값으로 저장, static block 실행

 

 

참고자료

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

https://medium.com/@gsy4568/jvm%EC%9D%98-%EC%B2%AB%EA%B4%80%EB%AC%B8-classloader-ecdf93d53a7b

https://blog.hexabrain.net/397

https://homoefficio.github.io/2019/01/31/Back-to-the-Essence-Java-%EC%BB%B4%ED%8C%8C%EC%9D%BC%EC%97%90%EC%84%9C-%EC%8B%A4%ED%96%89%EA%B9%8C%EC%A7%80-1/

https://velog.io/@ddangle/Java-%EB%9F%B0%ED%83%80%EC%9E%84-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%98%81%EC%97%ADRuntime-Data-Area%EC%97%90-%EB%8C%80%ED%95%B4

https://velog.io/@ariul-dev/%EC%B0%A8%EA%B7%BC%EC%B0%A8%EA%B7%BC-%EC%95%8C%EC%95%84%EB%B3%B4%EB%8A%94-Java-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%A8-%EC%8B%A4%ED%96%89-%EA%B3%BC%EC%A0%95