자바 코드(JAVA Code) 실행 과정
자바 코드의 실행 과정은 다음과 같습니다.
- 개발자가 자바 소스코드 파일(.java) 파일을 생성한다.
- 이를 자바 컴파일러(javac)로 빌드하여 바이트 코드(.class)로 컴파일한다.
- .class 파일을 JVM의 클래스 로더(Class Loader)에게 전달한다.
- 클래스 로더는 동적 로딩을 통해 필요한 클래스들을 로딩 및 링크하여 런타임 데이터 영역(Runtime Data Area) 인 JVM의 메모리에 올린다.
- 실행엔진(Execution Engine)은 JVM 메모리에 올라온 바이트 코드들을 명령어 단위로 하나씩 가져와 실행한다.
JVM 동작원리 및 기본개념
클래스 로더 (Class Loader)
● 특징
- 계층 구조
- 클래스 로더는 여러 클래스 로더 간 부모-자식 관계를 이룬 계층 구조로, 그 종류는 다음과 같습니다.
- 부트스트랩 클래스 로더 (Bootstrap Class Loader)
- 최상위 클래스 로더로 유일하게 JAVA가 아닌 네이티브 코드로 구현되어 있다.
- JVM이 실행될 때 같이 메모리에 올라간다.
- Object 클래스를 비롯하여 JAVA API들을 로드한다.
- 익스텐션 클래스 로더 (Extension Class Loader)
- 기본 JAVA API를 제외한 확장 클래스들을 로드한다. (다양한 보안/확장 기능 로드)
- 시스템 클래스 로더 (System Class Loader)
- (부트스트랩과 익스텐션 클래스로더가 JVM 자체의 구성요소들을 로드한다면,) 시스템 클래스 로더는 애플리케이션의 클래스들을 로드한다.
- 사용자가 지정한 $CLASSPATH 내의 클래스들을 로드한다.
- 사용자 정의 클래스 로더 (User-Defined Class Loader)
- 애플리케이션 사용자가 직접 코드 상에서 생성하여 사용하는 클래스 로더
- 웹 애플리케이션 서버(WAS) 같은 프레임워크는 사용자 정의 클래스 로더를 사용하여 클래스 로더의 위임 모델을 통해 애플리케이션의 독립성을 보장한다.
- 위임 모델
- 위임 모델은 {처음 바이트 코드를 넘겨받은 클래스 로더가 필요한 클래스를 로드할 때} 혹은 {실행엔진에서 명령어 단위로 바이트코드를 실행하다가 처음으로 참조하는 클래스에 대해 클래스 로더에게 로드를 요청할 때} 로드를 요청 받은 클래스 로더가 다음 순으로 "요청 받은 클래스가 있는지 확인"합니다.
- 1 - 클래스 로더 캐시
- 2 - 상위 클래스 로더
- 3 - 자기 자신
- 이전에 로드된 클래스인지 클래스 로더 캐시를 확인하고, 없으면 상위 클래스 로더를 하나씩 거슬러 오르며 확인합니다. 중요한 점은 올라가는 도중에 클래스를 발견하더라도 부트 스트랩 클래스 로더까지 계속 확인하고, 최상위 클래스 로더에 해당 클래스가 존재한다면 부트스트랩 클래스 로더에 있는 클래스를 로드합니다.
- 위임 모델은 {처음 바이트 코드를 넘겨받은 클래스 로더가 필요한 클래스를 로드할 때} 혹은 {실행엔진에서 명령어 단위로 바이트코드를 실행하다가 처음으로 참조하는 클래스에 대해 클래스 로더에게 로드를 요청할 때} 로드를 요청 받은 클래스 로더가 다음 순으로 "요청 받은 클래스가 있는지 확인"합니다.
- 가시성 제한
- 클래스 로더가 클래스 로드를 요청받았을 때 위임모델에 의해서 클래스 로더 캐시를 확인하고 없으면 상위 클래스 로더를 확인하는데, 이 때 하위 클래스 로더에 있는 클래스는 확인이 불가능합니다.
- 언로드(Unload) 불가
- 클래스를 로드하는 것은 가능하지만 언로드하는 것은 불가능하다.
- 이름공간 (Name Space)
- 네임스페이스는 각 클래스 로더들이 가지고 있는 공간으로, 로드된 클래스를 보관하는 공간입니다.
- 클래스를 로드할 때 위임 모델을 통해서 상위 클래스 로더들을 확인하는데 이때 확인하는 공간이 네임스페이스입니다.
- 패키지명까지 포함된 식별자(FQCN: Fully Qualified Class Name)을 기준으로 보관됩니다.
● 클래스 로드 과정
- 로드(Load) : 클래스 파일을 가져와 JVM의 메모리에 로드합니다.
- 검증(Verify) : 읽어들인 클래스가 {자바 언어 명세} 및 {JVM 명세}에 명시된 대로 구성됐는지 검사합니다.
- 준비(Prepare) : 클래스가 필요로 하는 메모리(클래스 정의 필드 / 메서드/ 인터페이스 데이터 구조 등)를 할당합니다.
- 분석(Resolve) : 클래스의 상수 풀 내 모든 심볼릭 레퍼런스를 다이렉트 레퍼런스로 변경합니다.
- 초기화(Initialize) : 클래스 변수들을 적절한 값으로 초기화합니다. (static 필드들을 설정된 값으로 초기화)
런타임 데이터 영역(Runtime Data Area)
JVM이 OS 위에서 실행되면서 할당받는 메모리 영역을 의미하며 크게 5가지, 세분화하면 6가지 영역으로 구분됩니다.
PC 레지스터(PC Register), 스택 영역(Stack Area), 네이티브 메서드 스택(Native Method Stack)은 스레드마다 하나씩 생성되고, 힙(Heap), 메서드 영역(Method Area)은 모든 스레드가 공유하며 GC의 대상입니다.
- PC 레지스터(PC Register)
- 현재 수행 중인 명령의 주소를 가집니다.
- 스레드가 시작될 때 생성되고 각 스레드마다 하나씩 존재합니다.
- 스택 영역 (Stack Area)
- 스택 프레임(Stack Frame) 구조체를 저장하는 스택입니다.
- 예외 발생 시 printStackTrace() 메서드로 보여주는 Stack Trace 각 라인 하나가 스택 프레임을 표현합니다.
- JVM 스택 역시 PC 레지스터와 마찬가지고 스레드가 시작될 때 생성되고 각 스레드마다 하나씩 존재합니다.
- 네이티브 메서드 영역 (Native Method Area)
- JAVA 외의 언어로 작성된 네이티브 코드를 위한 스택입니다.
- JNI(JAVA Native Interface)를 통해 호출하는 C/C++ 등의 코드를 수행하기 위한 스택으로, 언어에 맞게 스택이 생성됩니다.
- 힙 영역
- new 키워드로 생성된 객체와 배열이 저장되는 공간으로 가비지 컬렉션(Garbage Collection) 대상입니다.
- JVM 성능 등의 이슈에서 가장 많이 언급되는 공간으로, 힙 구성 방식이나 가비지 컬렉션 방법 등은 JVM 벤더들의 재량입니다.
- 메서드 영역 (Method Area, Static Area)
- 모든 스레드가 공유하는 영역으로 JVM이 시작될 때 생성됩니다.
- JVM이 읽어들인 각각의 클래스와 인터페이스에 대한 런타임 상수 풀, 필드와 메서드에 대한 정보, static 변수, 메서드의 바이트 코드 등을 보관합니다.
- 런타임 상수 풀 (Runtime Constant Pool)
- 각 클래스와 인터페이스의 상수 뿐만 아니라, 메서드와 필드에 대한 모든 레퍼런스까지 담고 있는 테이블입니다.
- 어떤 메서드나 필드를 참조할 때 JVM 상수 풀을 통해 해당 메서드나 필드의 실제 메모리상 주소를 찾아서 참조합니다.
- JVM 동작에서 가장 핵심적인 역할을 수행하는 곳으로 JVM 명세에서도 따로 중요하게 기술합니다.
실행 엔진(Execution Engine)
- 클래스 로더를 통해 런타임 데이터 영역에 배치된 바이트 코드를 명령어 단위로 읽어서 실행합니다.
- 바이트 코드의 각 명령어는 1바이트 크기의 OpCode(Operation Code)와 추가 피연산자로 구성됩니다.
- 실행 엔진은 하나의 OpCode를 가져와서 피연산자와 작업을 수행한 뒤, 다음 OpCode를 수행하는 식으로 동작합니다.
- 연산 작업 수행 중, 바이트 코드를 기계가 실행할 수 있는 형태로 변경하는데 그 방식은 다음의 두 가지입니다.
- 인터프리터
- 바이트 코드 명령어를 하나씩 읽어서 해석하고 실행합니다.
- 하나하나의 해석은 빠르지만 전체적인 실행 속도는 느립니다.
- JVM 안에서 바이트 코드는 기본적으로 인터프리터 방식으로 동작합니다.
- JIT 컴파일러 (Just-In-Time Compiler)
- 인터프리터의 단점을 보완하기 위해 도입된 방식으로, 바이트 코드 전체를 컴파일하여 네이티브 코드로 변경하고 이후에는 해당 메서드를 더 이상 인터프리팅하지 않고 네이티브 코드로 직접 실행하는 방식입니다.
- 덕분에 전체적인 실행 속도는 인터프리팅 방식보다 빠릅니다.
- JIT 컴파일 과정은 바이트 코드를 바로 네이티브 코드로 만드는 것이 아니라 안에서 IR(Intermediate Represenatation)로 변환하여 최적화를 수행하고 그 다음에 네이티브 코드로 변환합니다.
Q1. 자바 코드의 실행 과정에 대해 설명해주세요.
자바 소스 코드 파일(.java)를 자바 컴파일러(javac)를 통해 바이트 코드(.class)로 컴파일합니다. 해당 파일은 JVM의 클래스 로더에게 전달되어 동적 로딩을 통해 필요한 클래스들을 런타임 데이터 영역에 로드 및 링크합니다. 실행엔진은 JVM 메모리에 올라온 바이트 코드들을 명령어 단위로 하나씩 가져와 실행합니다.
Q2. JVM 클래스 로더의 특징에 대해 설명해주세요.
JVM 내의 클래스 로더는 계층 구조에 따라, 가장 상위의 부트스트랩 클래스 로더부터, 확장 클래스 로더, 시스템 클래스 로더, 그리고 사용자 정의 클래스 로더로 구성됩니다. (계층 구조)
클래스 로더는 클래스 자체를 로딩하기 전에 위임 모델에 따라 그 부모 클래스 로더에게 클래스 로딩 작업을 위탁하여 불필요한 복제를 방지합니다. (위임 모델)
또한, 한번 로드된 클래스는 언로드가 불가합니다. (언로드 불가)
클래스 로더는 클래스 로드를 요청받았을 때 위임 모델에 의해 클래스 로더 캐시를 확인하고 없으면 상위 클래스 로더를 확인하는데, 이 때 하위 클래스 로더에 있는 클래스는 확인이 불가능합니다. (가시성 제한)
각 클래스 로더는 로드된 클래스를 보관하는 공간인 네임스페이스를 보유하는데, 클래스 로드 시 위임 모델에 따라 해당 공간을 확인합니다. (네임스페이스)
Q3. 클래스 로더가 클래스를 로딩하는 과정에 대해 설명해주세요.
Q4. 인터프리터 방식과 컴파일 방식을 비교해주세요.
참고 출처
'프로그래밍 언어 > Java' 카테고리의 다른 글
[Java] OOP의 4가지 특징 (0) | 2024.01.23 |
---|---|
[Java] 자바 접근 제어자의 유형과 특징 (0) | 2024.01.22 |
[Java] String vs. StringBuilder vs. StringBuffer 차이 (0) | 2024.01.18 |
[Java] Garbage Collector란? (1) | 2023.11.06 |
[Java] JRE & JDK & JVM (0) | 2023.10.17 |