서론
Java 프로그램을 실행하고 관리하는 JVM 에 관하여 찾아보던 중 이 강의를 알게 되어 정리하였다. Java 런타임을 이해하고, 추후에는 JVM 튜닝에도 활용할 수 있을 것이다. 대부분의 용어는 영문을 그대로 사용하였다. Java 파일을 컴파일한 claas 파일, 즉 Bytecode 생성 이후의 일들을 다룬다.
목차
- Java Runtime(실행환경)
- Classloading Engine
- Execution Engine
- Meta Information subsystem
- Threading Synchronization
- Memory Management
Java Runtime
JVM만으로는 Java 바이트코드를 실행할 수 없다. 플랫폼 클래스와 그 네이티브 메소드의 구현체, 보조 파일을 포함한 JRE도 함께 필요하다. (참고로 개발환경에는 이에 더하여 Java Compiler 도 필요하다, JDK)
Classloader Engine
모든 클래스는 클래스로더에 의해 로드된다.
- 플랫폼 클래스는 부트스트랩 클래스로더에 의해 로드된다
- 애플리케이션 클래스패스의 클래스는 시스템 클래스로더에 의해 로드된다
- 애플리케이션 클래스는 사용자 정의 클래스로더를 생성할 수 있다.
질문: JVM은 완전히 같은 가능한 이름을 가진 서로 다른 두 클래스를 로드할 수 있을까?
정답은 '그렇다'이다. 각각의 클래스로더가 네임스페이스를 갖기 때문이다.
JVM 의 초기화 단계에서 메인 클래스가 시스템 클래스로더에 의해 로드되고 (이 과정에서 플랫폼 클래스의 일부를 로딩하도록 한다). 메인 클래스의 public static void main(string[] args) 메서드가 실행된다.
클래스 로딩 프로세스는 다음을 따른다.
- 클래스파일 파싱
- JVM의 특별한 메모리영역에 클래스의 런타임 표현(representation) 을 생성
- 런타임 상수 풀은 메서드영역(a.k.a 메타 영역, 퍼매넌트 영역)에 있다
- 슈퍼(부모)클래스와 슈퍼인터페이스를 로드
다음으로는 Java 바이트코드를 검증하고, 심볼릭 참조를 실제 메모리 주소로 리졸브하는 링크 단계를 거 클래스의 스태틱 이니셜라이저를 호출하는 클래스 초기화가 수행된다. 클래스 초기화는 다음 상황의 첫 번째 사용 시에 일어나며, 해당 클래스의 슈퍼클래스와 슈퍼인터페이스(디폴트 메서드 포함)의 초기화를 하도록 한다.
- new
- 스태틱 필드 접근
- 스태틱 메서드 호출
Execute Engine
최종적으로 JVM은 바이트 코드를 interpret하고 이를 네이티브 코드(cpu에서 바로 실행가능한)로 translate하여 실행한다. interpretation 속도를 증가시켜 실행속도를 빠르게 하기 위하여 다음의 변화가 있었다.
- 템플릿 인터프리터(와 트랜스레이터)
- 바이트코드에 해당하는 테이블로 점프
- 컴파일러로 인터프리터와 트랜스레이터를 대체
- jit(just in time) 컴파일
- 자주 사용되는 핫스팟만을 미리 컴파일
- aot(ahead of time) 컴파일
- jit(just in time) 컴파일
Meta Information subsystem
메타 정보 서브시스템은 메타 영역을 이용하여 reflection 과 JNI를 구현한다. 리플렉션은 런타임에 클래스, 필드, 메서드를 스트링 리터럴인 이름으로 접근하는 것을 허용한다. 많은 유명한 프레임워크와 JVM 기반 프로그래밍 언어 구현체의 핵심 기능이다. JNI(Java Native Interface)는 JVM을 OS와 바인딩하는 C로 구현된 인터페이스이다. 아래에 리플렉션의 활용 예시를 추가적으로 정리하였다.
- Spring 프레임워크의 DI(Dependency Injection)
- 리플렉션을 사용하여 클래스의 필드나 메서드에 객체를 동적으로 주입합니다.
- JDBC 드라이버 로드
- 런타임에 동적으로 클래스를 로드하고 실행
- JUnit 프레임워크의 테스트
- 리플렉션으로 테스트 메서드를 탐색하고 실행
Threading Synchronization - Multi threading
자바의 스레드 관련 기능은 java.lang.Thread 클래스에 구현되어있다. 자바의 스레드는 네이티브 스레드와 일대일로 매핑되어있으며, 각 스레드는 예약된 메모리 영역(로컬 변수와 실행중인 메서드의 스택이라고 불림)을 갖는다. 자바의 스레드는 stack trace 라는 스택에 대한 상세 정보를 가지고 있고, -Xss라는 아규먼트로 설정 가능하다.
스레드 간 공유되는 메모리에 안전하게 접근하기 위해 동기화가 필요하다. critical section 과 같은 운영체제의 동기화를 위한 저수준 메커니즘(Primitive)를 사용할 수도 있지만, 오늘날 java.util.concurrent 패키지를 사용하는 것이 추천된다.
Memory Management
new 연산자는 객체를 Java heap이라고 불리는 메모리에 할당하도록 구현되어 있다. 자바 객체의 레이아웃은 JVM 스펙에 명시되어있지는 않지만 다음을 요구한다.
- Java 객체 헤더
- 클래스 객체에 대한 참조
- 모니터
- 식별 해시코드
- gc 플래그
- 필드
- 필드는 타겟 아키텍처 명세에 따라 크기 최적화, alignment 등을 목적으로 재정렬될 수 있다.
가비지가 무엇인지 정의하기 위해 가비지가 아닌것을 먼저 정의하면, GC(Garbage Collection) root의 객체이면서 가비지가 아닌 객체로부터 참조당하는 객체이다. 가비지는 이 여집합이다. GC roots set은 다음과 같다.
- 클래스의 스태틱 필드의 객체
- 실행중인 스레드 스택에서 접근 가능한 객체
- 네이티브 메서드의 JNI 에서 참조하는 객체
가비지 컬렉팅 및 제거하는 방법에는 mark-and-sweep방식과 stop-and-copy 방식이 있다. 그런데 살아있는 객체는 프로그램 실행의 특정 시점에 정의되는 것이므로, 실행되는 동안 계속 변화하여, 가비지 컬렉팅을 위해 모든 스레드가 중지되어야 하는 문제가 있으며 이를 stop the world 라고 한다. 이를 줄이기 위한 방법에는 incremental, parallel, concurrent 전략이 있다.
요약

출처
'배운 것들 > 언어' 카테고리의 다른 글
| JavaScript 심볼이 JSON stringify 되지 않는 이유와 대응 (3) | 2025.05.03 |
|---|---|
| 자바스크립트 변수의 유효범위와 클로저 (모던자바스크립트 요약, 과제 풀이) (5) | 2024.12.08 |