Java 소스 코드의 실행 과정
요약
- 자바 파일(
.java
)은 javac(=java compiler)에 의해 바이트 코드(.class
)로 컴파일된다.JVM의 클래스 로더(Class Loader)가 필요한 클래스들을 로드 및 링크하여 JVM 메모리(Runtime Data Area)에 올린다.- Java는 런타임(컴파일 타임 X)에 클래스를 처음 참조할 때에는 해당 클래스를 로드하고 링크하는 동적 로드를 한다. 이 동적 로드를 수행하는 부분이 JVM의 클래스 로더이다.
- 클래스 로더의 특징
- 계층 구조 : 부트스트랩 클래스 로더 > Extension 클래스 로더 > System 클래스 로더 순
- 위임 모델 : 클래스 로더끼리 로드를 위임하는 구조로 동작한다. (이전에 로드 된 클래스인지 확인하기 위해 클래스 로더 캐시를 확인 → 없는 경우 상위 클래스 로더를 거슬러가며 확인 → 자기 자신을 확인) 이 때, 상위 클래스 로더에 있는 경우 시스템 클래스 로더에 있어도 상위 클래스 로더에 있는 클래스를 로드한다.
- 클래스 로더의 종류
- 부트스트랩 클래스 로더 : JVM을 기동할 때 생성되며, Object 등 Java API 들을 로드한다. (네이티브 코드로 구현되어 있다.)
- 익스텐션 클래스 로더 : 기본 자바 API를 제외한 확장 클래스들을 로드한다.
- 시스템 클래스 로더 : 애플리케이션의 클래스들을 로드한다.(사용자가 지정한 $CLASSPATH 내의 클래스들을 로드)
- 사용자 정의 클래스 로더
- 동적 로드 방식
- 로드(Load) : 클래스를 파일에서 가져와 JVM 메모리에 로드한다.
- 검증(Verifying) : 자바 언어 명세(Java Language Specification) 및 JVM 명세에 명시된 대로 잘 구성됐는지 검사한다.
- 준비(Preparing) : 클래스가 필요로 하는 메모리를 할당하고, 클래스의 멤버들을 구성하는데 필요한 데이터 구조를 준비한다.
- 분석(Resolving) : 클래스의 상수 풀 내 모든 심볼릭 레퍼런스를 다이렉트 레퍼런스로 변경한다. 🤨
- 초기화 : 클래스 변수들을 적절한 값으로 초기화한다.
- Runtime Data 영역 : JVM이 OS위에서 실행되면서 할당받는 메모리 영역
- 실행 엔진(Execution Engine)은 클래스 로더를 통해 런타임 데이터 영역에 올라온 바이트 코드를 명령어 단위로 읽고 실행시킨다.
- 이 과정에서 실행 엔진은 인터프리터와 JIT 컴파일러를 이용해 바이트 코드를 기계어로 번역한다.
- 인터프리터 vs JIT 컴파일러
- 인터프리터 : 한 줄씩 읽고 기계어로 번환한다.
- JIT(Just In Time) 컴파일러 : 인터프리터의 단점을 보완하기 위해 나온 컴파일러로, 바이트 코드 전체를 컴파일해 네이티브 코드로 변경하고, 이후에는 해당 메서드를 더이상 인터프리트하지 않고 네이티브 코드 자체로 실행하는 방식이다. 이 과정에서 최적화도 함께 일어나게 되빈다. 전체적인 실행 속도는 인터프리터보다 빠르다.
- Cf. JIT 컴파일러가 컴파일하는데 걸리는 시간은 바이트 코드를 한 줄씩 인터프리트하는 시간보다 훨씬 오래 걸리기 때문에 JIT 컴파일러를 사용하는 JVM은 내부적으로 해당 메서드가 얼마나 자주 호출되고 실행되는지 체크해 일정 기준을 넘었을 때만 JIT 컴파일러를 통해 컴파일해 네이티브 코드로 변환한다.
- 인터프리터 vs JIT 컴파일러
- 이 과정에서 실행 엔진은 인터프리터와 JIT 컴파일러를 이용해 바이트 코드를 기계어로 번역한다.
Java의 Runtime Data Area
PC Register, JVM Stack, Native Method는 스레드 별로 생성되고, 나머지 Heap, Method Area, Runtime Constant Pool은 모든 스레드가 공유한다.
구분
- 힙(Heap) : 인스턴스를 저장하는 영역. GC의 대상이다.
- 메서드 영역(Method Area) : JVM이 읽어 들인 각 클래스와 인터페이스에 대한 런타임 상수 풀, 필드와 메서드 정보, Static 변수, 메서드의 바이트코드 등을 보관한다. 오라클의 Hotspot JVM에서는 이 영역을 펌 영역(PERM, Permanent Area)라고도 한다.
- 런타임 상수 풀(Runtime Constant Pool) : 메서드 영역에 포함되는 영역이나 핵심적인 역할을 수행하는 영역으로 따로 기술. 각 클래스 & 인터페이스의 상수, 메서드와 필드에 대한 모든 레퍼런스를 가지고 있는 테이블이다. 어떤 메서드나 필드를 참조할 때 JVM은 런타임 상수 풀을 통해 해당 메서드나 필드의 실제 메모리상 주소를 찾아서 참조한다.
- PC 레지스터(PC Register) : PC(Program Counter) 레지스터는 현재 수행 중인 명령의 주소를 가지며, 스레드가 시작될 때 생성되고 한 스레드 당 하나씩 존재한다.
- JVM 스택(JVM Stack) : 스택 프레임(Stack Frame)이라는 구조체를 저장하는 스택이다.
printStackTrace()
메서드의 결과로 보이는 Stack Trace 라인 하나가 스택 프레임이다. PC 레지스터와 마찬가지로 스레드가 생성될 때 생성되며 한 스레드 당 하나씩 존재한다. - 네이티브 메서드(Native Method) : Java 외의 네이티브 언어로 작성된 네이티브 코드를 수행하기 위한 스택이다. 언어에 맞게 생성된다.
Java의 메모리 구조 & GC
Hotspot ‘Heap’ Structure
오라클(Oracle) Hotspot JVM을 기준으로 힙 영역은 크게 Perm(Permanent Generation), Young(Eden, Servivor), Old 영역으로 구분된다.
Minor GC
객체는 처음 생성되면 Young 영역에 존재한다. Young 역역에서 GC가 발생하면 이를 Minor GC라고 한다. 생성 된지 얼마 되지 않은 객체들은 Eden에 존재하는데, Eden 영역이 꽉차면 Minor GC가 발생한다.
Minor GC이 이루어지고 한 후에도 살아남은 객체는 Eden, Survivor0에서 Survivor1로 옮겨진다. 이후 다음 Minor GC가 발생하고 나서 살아남은 객체들은 Eden과 Survivor1에서 Survivor0으로 이동한다. 이런 식으로 Minor GC가 발생할 때마다 살아남은 객체들은 Survivor0, Survivor1를 왔다갔다하다가 Survivor 영역에서 오래 살아남은 객체는 Old 영역으로 넘어가게 되는 것이다.
Cf. Survivor0, Survivor1의 숫자는 단순 구분을 위할 뿐 순서를 의미하지 않는다. 또한, Survivor0과 Survivor1 중 한 영역은 반드시 비어있는 상태이다.
Cf. Minor GC가 발생할 때마다 살아남은 객체의 Age는 증가하는데, Age가 임계치를 넘은 객체는 Old 영역으로 이동한다.
Full GC
Old 영역에서 발생하는 GC를 Full GC 또는 Major GC이라고 한다. Old 영역이 꽉 차게 되면 Full GC가 발생한다. Full GC이 발생하게 되면 Java 애플리케이션은 아무 동작도 하지 않고 중단된다. 때문에 Full GC이 일어나는 시간이나 빈도를 최소화하는 것이 중요시된다.
- Full CG의 발생 빈도를 줄이기 위해 Old 영역을 늘리면, Full GC 빈도는 줄어도 발생 시간이 길어진다. 반대로 Full GC의 발생 시간을 줄이기 위해 Old 영역을 줄이면 Full GC의 발생 빈도가 늘어나게 된다. 따라서, 시스템에 맞는 적절한 수준을 찾는 것이 중요하다.
- 서버의 경우 대부분 로드밸런서를 이용해 고가용성을 유지하는 것이 좋다. 로드밸런서를 사용해 서버를 여러 대 둘 경우, Full GC이 발생해 성능이 저하되는 동안 다른 서버로 트래픽을 전달해 성능 저하를 최소화할 수 있을 것으로 보인다.
참고 글
'Backend > Java' 카테고리의 다른 글
Comparable과 Comparator (0) | 2024.08.15 |
---|---|
(메모) Map -> Set -> Iterator 변환하기 (0) | 2024.07.24 |
Java) 어노테이션(Annotation)이란? (0) | 2022.12.26 |
Java8) Annotation 변경 사항(타입 선언부에 사용, @Repeatable) (0) | 2022.12.26 |
Java8) Java 비동기 프로그래밍과 CompletableFuture (0) | 2022.10.31 |