02. JVM 구조
JVM 구조
현재 JVM에서 가장 보편적으로 사용되는 구조는 핫스팟 가상 머신 구조입니다. 서버상에서 장시간 운영되는 애플리케이션을 위한 구조입니다.
가볍고 부하가 적으며, GC 최적화를 잘해 자바의 성능이 아쉽다는 평을 많이 지우는 데 기여했습니다.
먼저 핫스팟의 구조를 봅시다.
먼저 JVM은 스택 기반의 해석 머신입니다. 물리적인 하드웨어(레지스터)는 없으나, 일부 결과(중간 값)를 스택에 보관하며, 이 스택의 맨 위에 쌓인 값(마지막에 실행된 명령어)들을 가져와 계산을 실시하죠.
단계적으로 설명하자면
1. OS의 메모리 할당
2. 가상 머신 프로세스(자바 바이너리) 실행
3. 자바 가상 환경 구성 및 스택 머신 초기화
4. 컴파일된 클래스 파일 로드 (값들을 스택 영역에 저장)
5. 계산 실시
이 과정들이 모두 java라는 한 명령어로 실행이 되는 것이죠.
java HelloWorld
그럼 하나하나 알아보도록 합시다!
1. 클래스 로더
The Java Virtual Machine dynamically loads, links and initializes classes and interfaces. Loading is the process of finding the binary representation of a class or interface type with a particular name and creating a class or interface from that binary representation. Linking is the process of taking a class or interface and combining it into the run-time state of the Java Virtual Machine so that it can be executed. Initialization of a class or interface consists of executing the class or interface initialization method <clinit> (§2.9).
JVM은 클래스와 인터페이스를 동적으로 로드 연결 및 초기화를 할 수 있습니다.
로딩(Loading)은 특정 이름을 가진 클래스나 인터페이스 유형의 이진 표현을 찾고, 해당 이진 표현에서 클래스 또는 인터페이스를 만드는 프로세스입니다.
연결(Linking)은 클래스나 인터페이스를 가져와, JVM의 런타임 상태로 결합해 실행할 수 있도록 하는 프로세스입니다.
초기화(initialization)는 클래스 또는 인터페이스 초기화 메서드 <clinit>를 실행하는 프로세스입니다.
로딩
Creation of a class or interface C denoted by the name N consists of the construction in the method area of the Java Virtual Machine (§2.5.4) of an implementation-specific internal representation of C.
Class or interface creation is triggered by another class or interface D, which references C through its run-time constant pool.
Class or interface creation may also be triggered by D invoking methods in certain Java SE platform class libraries (§2.12) such as reflection.
If C is not an array class, it is created by loading a binary representation of C (§4) using a class loader. Array classes do not have an external binary representation; they are created by the Java Virtual Machine rather than by a class loader.
클래스 파일을 실행하기 위해서는, 일단 로드부터 실시해야 합니다. 이때가 바로 '클래스 로더'가 활약할 시간입니다.
(어렵게 생각하실 필요가 없습니다. 그냥 최초로 실행되는 객체라고 생각하시면 됩니다.)
클래스 로더는 자바 프로세스가 초기화되면서 연쇄적으로 작동하기 시작합니다. (스펙에서는 Trigger로 표현되어 있습니다.)
주의사항
When one class loader delegates to another class loader, the loader that initiates the loading is not necessarily the same loader that completes the loading and defines the class. If L creates C, either by defining it directly or by delegation, we say that L initiates loading of C or, equivalently, that L is an initiating loader of C.
At run time, a class or interface is determined not by its name alone, but by a pair: its binary name (§4.2.1) and its defining class loader. Each such class or interface belongs to a single run-time package. The run-time package of a class or interface is determined by the package name and defining class loader of the class or interface.
한 클래스 로더가 다른 클래스 로더에 위임할 때 로더는, 일단 로딩을 시작하면 반드시 로딩을 완료해야 합니다.
위임받은 로더가 클래스를 정의하는 동일한 로더일 필요는 없습니다.
자바는 클래스를 로드할 때 런타임 환경에서 해당 클래스나 인터페이스 객체를 생성합니다.
이 객체들은 이름만으로 결정되는 것이 아니라, (1) 바이너리 이름(패키지 주소를 포함하는 풀 클래스 명)과 (2) 클래스 로더를 정의하는 한 쌍으로 결정이 됩니다. 그렇기 때문에, 똑같은 클래스명이지만, 클래스 로더가 다르면 다른 객체이니 이를 주의해야 합니다. (인스턴스 생성 시 이름@주소 느낌)
1. 부트스트랩 클래스 로더
The following steps are used to load and thereby create the nonarray class or interface C denoted by N using the bootstrap class loader.
First, the Java Virtual Machine determines whether the bootstrap class loader has already been recorded as an initiating loader of a class or interface denoted by N. If so, this class or interface is C, and no class creation is necessary.
Otherwise, the Java Virtual Machine passes the argument N to an invocation of a method on the bootstrap class loader to search for a purported representation of C in a platform-dependent manner. Typically, a class or interface will be represented using a file in a hierarchical file system, and the name of the class or interface will be encoded in the pathname of the file.
Note that there is no guarantee that a purported representation found is valid or is a representation of C. This phase of loading must detect the following error:
If no purported representation of C is found, loading throws an instance of ClassNotFoundException.
Then the Java Virtual Machine attempts to derive a class denoted by N using the bootstrap class loader from the purported representation using the algorithm found in §5.3.5. That class is C.
가장 먼저 실행되는 클래스입니다. 이 부트스트랩 클래스의 주 목표는, 다른 클래스 로더가 나머지 시스템이 필요한 클래스를 로드할 수 있게 최소한의 필수 클래스(java.lang.Object, Class, Classloader)만 로드하는 것입니다.
2. 확장 클래스 로더
부트스트랩 클래스 로더를 슈퍼클래스로 설정하고 필요할 때 로딩 작업을 부트스트랩에게 넘깁니다. 자주 쓰이지는 않지만, 특정 OS나 플랫폼에 네이티브 코드(OS가 직접 컴파일 가능한 코드)를 제공하거나 기본 환경을 오버라이드 할 때 사용합니다.
자바스크립트 런타임 내시 혼(Nashorn)이 가장 대표적인 예시입니다.
3. 애플리케이션 클래스 로더 ('시스템' 클래스 로더 X)
마지막으로 확장 클래스 로더를 슈퍼클래스로 둔 애플리케이션 클래스 로더가 생성되고, 지정된 클래스 패스에 위치한 유저 클래스를 로드합니다.
실제로 시스템 클래스는 로드하지 않기 때문에 '시스템'이라는 말은 빼자
4. 기타
프로그램 실행 중 처음 보는 새 클래스를 디펜던시에 로드합니다. 클래스를 찾지 못한 클래스 로더는 기본적으로 자신의 부모 클래스 로더에게 대신 룩업을 넘깁니다.
이 과정이 반복돼서 부트스트랩도 룩업 하지 못한다면, ClassNotFoundException 예외가 발생합니다. 그렇기 때문에, 빌드 프로세스 수립 시 운영 환경과 동일한 클래스 패스로 컴파일하는 것이 좋습니다.
연결
Linking a class or interface involves verifying and preparing that class or interface, its direct superclass, its direct superinterfaces, and its element type (if it is an array type), if necessary. Resolution of symbolic references in the class or interface is an optional part of linking.
This specification allows an implementation flexibility as to when linking activities (and, because of recursion, loading) take place, provided that all of the following properties are maintained:
A class or interface is completely loaded before it is linked.A class or interface is completely verified and prepared before it is initialized.Errors detected during linkage are thrown at a point in the program where some action is taken by the program that might, directly or indirectly, require linkage to the class or interface involved in the error.
For example, a Java Virtual Machine implementation may choose to resolve each symbolic reference in a class or interface individually when it is used ("lazy" or "late" resolution), or to resolve them all at once when the class is being verified ("eager" or "static" resolution). This means that the resolution process may continue, in some implementations, after a class or interface has been initialized. Whichever strategy is followed, any error detected during resolution must be thrown at a point in the program that (directly or indirectly) uses a symbolic reference to the class or interface.
Because linking involves the allocation of new data structures, it may fail with an OutOfMemoryError.
클래스나 인터페이스를 연결하려면 해당 클래스나 인터페이스, 해당 슈퍼클래스나 슈퍼 인터페이스 및 해당 요소 유형(배열 유형인 경우)을 필요한 경우 확인하고 준비해야 합니다.
이 기능 덕분에 구현의 유연성을 허용합니다. 연결을 위해서는 완전히 로드되어야 합니다.
초기화
클래스 또는 인터페이스의 초기화는 해당 클래스 또는 인터페이스 초기화 메서드를 실행하는 것으로 구성됩니다.
구체적인 내용은 다음 링크를 참고하시면 됩니다.
2. 실행 엔진
JIT(Just In Time) 컴파일
자바 프로그램은 스택의 맨 윗 값(명령어)을 실행하면서 시작됩니다.
JVM은 CPU를 추상화했기 때문에, 다른 플랫폼에서도 문제없이 실행이 가능하나, 성능을 최대로 내기 위해서는 네이티브 기능을 활용해 CPU에서 직접 프로그램을 실행해야 합니다.
이를 위해 핫스팟은 프로그램 단위(메서드&루프)를 인터프리트 된 바이트코드에서 네이티브 코드로 컴파일을 실시합니다. 이것이 바로 JIT(Just In Time) 컴파일입니다
핫스팟은 애플리케이션을 모니터링하면서 가장 자주 실행되는 코드 파트를 찾아 JIT 컴파일을 수행합니다.
애플리케이션을 실행하면 할수록 더욱 정교한 최적화를 실시할 수 있습니다.
장점
JIT의 최대 장점은 컴파일러가 해석 단계에서 수집한 추적 정보를 근거로 최적화를 결정한다는 것입니다.
이는 최적화의 방향 설정에 확실한 근거가 되기 때문에, 의도치 않은 최적화를 막을 수 있습니다.
GC
C, C++에서는 메모리 할당과 해제를 개발자들이 직접 합니다. 하지만, 자바는 Garbage Collector라는 프로세스를 이용해 힙 메모리를 자동 관리하는 방식으로 이를 해결합니다.
JVM이 메모리를 추가적으로 할당해야 할 때 불필요한 메모리를 회수하거나 재사용하는 불확정적 프로세스입니다.
참고자료
Spec - The Java® Virtual Machine Specification (oracle.com)
The Java® Virtual Machine Specification
Tim Lindholm Frank Yellin Gilad Bracha Alex Buckley
docs.oracle.com
도서 - Optimizing Java