-
JVM이란
자바 가상 머신인 Java Virtual Machine의 약자를 말한다.
가상 머신: 소프트웨어를 통해 구현된 컴퓨터 환경으로 실제 하드웨어와는 독립적으로 동작한다.
자바파일은 기본적으로 Java Compiler를 통해 바이트코드라는 형태의 중간 언어로 컴파일이 된다. 이 바이트코드는 JVM에서 실행이 되는데, JVM은 이 바이트코드를 해당 플랫폼의 기계어로 변환하고 실행한다. 이런 특성으로 자바 프로그램은 운영체제나 하드웨어에 의존하지 않고 여러 플랫폼에서 실행될 수 있으며, 특히 JVM은 자바의 핵심원칙 중 하나인 "Write once, Run Anywhere"의 핵심 역할을 수행한다.
JVM구조
JVM은 크게 Class Loader, Execution Engine, Runtime Data Area로 나뉘어져 있다.
Class Loader
Class Loader는 Loading, Linking, Intitialization의 과정을 거치며 .java 파일에서 컴파일된 .class파일을 메모리의 method Area에 로드하는 역할을 수행한다.
1) Loading
- Bootstrap ClassLoader
가장 최상위의 클래스 로더로써 가장 최우선 로드된다.
jre의 lib폴더에 잇는 rt.jar파일을 찾아 기본 자바 API라이브러리 메모리에 로드한다.
rt.jar파일에는 런타임 시 Java에서 제공되는 java.lang.String, java.lang.Object같은 모든 라이브러리가 포함되어 있다.
(rt = Runtime)
다른 클래스 로더와 달리 자바가 아니라 네이티브 코드로 구현되어 있다. - Extention ClassLoader
Bootstrap ClassLoader의 child이다.
Java8 에서는 Extention ClassLoader라고 불리며 Java9에서는 Platform ClassLoader라고 불린다.
기본 자바 API를 제외한 확장 클래스들을 로드한다. 다양한 보안 확장 기능 등을 여기에서 로드하게 된다.
$JAVA_HOME/jre/lib/ext 경로에 위치해 있는 자바의 확장 클래스들(기본 자바 API를 제외한 확장 클래스)을 메모리에 로딩하는 역할을 한다. ( JDBC와 같은 외부 라이브러리를 들을 로딩) - Application ClassLoader
Extension ClassLoder의 child이다.
Java8 에서는 Application ClassLoader라고 불리며 Java9에서는 System ClassLoader라고 불린다.
부트스트랩 클래스 로더와 익스텐션 클래스 로더가 JVM 자체의 구성 요소들을 로드하는 것이라 한다면, 시스템 클래스 로더는 애플리케이션의 클래스들을 로드한다고 할 수 있다
애플리케이션을 실행할 때 주는 -classpath 옵션 또는 $CLASSPATH에 설정된 경로를 탐색하여 그곳에 있는 클래스들을 메모리에 로딩하는 역할을 한다.
위임방식
2) Linking
- 검증(Verify)
가장 시간이 많이 걸리는 과정으로 읽어 들인 클래스가 자바 언어 명세(Java Language Specification) 및 JVM 명세에 명시된 대로 잘 구성되어 있는지 검사한다. - 준비(Prepare)
static int count = 0; 과 같은 정적 변수에 대한 메모리에 기본값이 할당된다. - 해석(Resolve)
심볼릭 메모리 레퍼런스를 메서드 영역에 있는 실제 레퍼런스(실제 메모리 주소)로 교환한다.
심볼릭 메모리 레퍼런스 : 기본 자료형을 제외한 모든 타입(클래스와 인터페이스)을 명시적인 메모리 주소가 아닌 주소의 이름을 통해 참조한다.
3) Initialization
준비 단계에서 확보한 메모리 영역의 모든 정적 변수에 자바 코드에 명시된 값이 할당된다.
Runtime Data Area
자동차가 달리기 위해서 도로가 필요한 것처럼 JVM에 .class파일을 저장하고 실행하기 위해서는 메모리 영역이 필요하다.
.class 파일은 클래스 로더에 의해서 JVM내로 로드가 되고, 실행엔진에 의해서 기계어로 해석되어 메모리 상(Runtime Data Area)에 배치된다.
즉, 런타임 시 클래스 데이터와 같은 메타 데이터와 실제 데이터가 저장되는 곳이다.
JVM은 OS에서 할당받은 메모리 영역(Runtime data aeras)을 메서드, 힙, 스레드 세가지 영역으로 구분한다.
1) 메서드(Method)
메서드 영역은 모든 스레드가 공유하는 영역으로 JVM이 시작될 때 생성된다.
JVM이 읽어 들인 각각의 클래스와 인터페이스에 대한 런타임 상수 풀, 필드와 메서드 정보, Static 변수, 메서드의 바이트코드 등을 보관한다.- 런타임 상수 풀(Runtime constant pool)
클래스 파일 포맷에서 constant_pool 테이블에 해당하는 영역이다.
메서드 영역에 포함되는 영역이긴 하지만, JVM 동작에서 가장 핵심적인 역할을 수행하는 곳이다.
각 클래스와 인터페이스의 상수뿐만 아니라, 메서드와 필드에 대한 모든 레퍼런스까지 담고 있는 테이블이다.
즉, 어떤 메서드나 필드를 참조할 때 JVM은 런타임 상수 풀을 통해 해당 메서드나 필드의 실제 메모리상 주소를 찾아서 참조한다.
2) 힙(Heap)
객체/배열의 저장 공간으로 JVM 내의 모든 Thread가 공유한다.
모든 객체 밑 인스턴스 변수는 이 메모리에 저장된다.
Memory 해제는 Garbage Collection을 통해서만 가능하며 사용되지 않는 개체는 GC가 자동으로 제거한다3) 스레드(Thread)
- 스택(Stack)
스레드 별로 스택이 생성된다.
멀티스레드를 사용한다면 멀티스레드의 개수만큼의 스택이 생성된다.
메서드를 호출할 때 마다 Frame을 스택에 추가(push)되고 메서드가 종료되면 Frame을 제거(pop)합니다.
지역 변수, 연산자 스택, 프레임 데이터가 저장됩니다. - PC레지스터(PC register)
PC(Program Counter) 레지스터는 각 스레드마다 하나씩 존재하며 스레드가 시작될 때 생성된다.
PC 레지스터는 실행중인 메서드 안에서 몇 번째 줄을 실행해야 하는지 나타내는 역할을 한다.
(현재 수행 중인 JVM 명령의 주소를 갖는다. - 네이티브 메서드 스택(Native Method Stack)
자바 외의 언어로 작성된 네이티브 코드를 위한 스택이다.
즉, JNI(Java Native Interface)를 통해 호출하는 C/C++ 등의 코드를 수행하기 위한 스택이다.
Execution Engine
실행엔진(Execution Engine)은 바이트 코드를 기계어로 변환하고 명령어를 실행하는 실제 엔진이다.
클래스 로더를 통해 JVM 내의 런타임 데이터 영역에 배치된 바이트코드는 실행 엔진에 의해 실행된다. 실행 엔진은 자바 바이트코드를 명령어 단위로 읽어서 실행한다.그러나 자바 바이트 코드는 기계가 바로 수행할 수 있는 언어보다는 비교적 인간이 보기 편한 형태로 기술된 것이다.
그래서 실행엔진은 이와 같은 바이트 코드를 실제로 JVM내부에서 기계가 실행할 수 있는 형태로 변경하는데,
그 방식이 인터프리터와 JIT컴파일러다.1) 인터프리터
인터프리터는 컴파일러처럼 사람이 이해할 수 있는 언어를 기계어로 해석해주는 번역 프로그램이다.
인터프리터는 바이트코드 명령어를 하나씩 읽어서 해석하고 실행한다. 하나씩 해석하고 실행하기 때문에 바이트코드 하나하나의 해석은 빠른 대신 인터프리팅 결과의 실행은 느리다는 단점을 가지고 있다.
2) JIT(Just-in-time) 컴파일러인터프리터의 단점을 보완하기 위해 도입된 것이 JIT 컴파일러이다.
따라서 초기에 인터프리터 방식으로 바이트 코드로 변환되며 그 코드를 캐싱하여, 같은 함수가 여러 번 불릴 때 캐싱된 값을 사용한다.
하지만 한 번만 실행되는 코드는 인터프리터 방식이 훨씬 유리하다.
따라서 JIT 컴파일러를 사용하는 JVM은 내부적으로 해당 메서드가 자주 수행되는지를 체크하여 일정 정도가 넘을 때만 JIT 컴파일을 수행한다.
또한 JIT 컴파일러는 컴파일 과정에서 최적화를 수행합니다
3) Garbage Collector
GC(GarbageCollector)는 프로그램이 동적으로 할당했던 메모리 영역(Heap)중 더 이상 사용되지 않는 객체를 삭제한다. C/C++은 사용자가 직접 메모리를 할당하고 해제하지만 JAVA는 GC가 해당작업을 수행한다.
- System.gc()나 Runtime.getRuntime().gc()를 통해 GC를 명시적으로 호출하는 것
해당 메서드는 Full GC를 수행하시키는 메서드이며 Stop the world 시간이 길고 무거운 작업이며 즉시 수행한다는
보장이 없기 때문에 사용을 지양해야한다.
참고 블로그
https://junuuu.tistory.com/126#recentComments'Java' 카테고리의 다른 글
@Transactional이란 무엇인가 (0) 2023.11.22 생성자 주입(Constructor Injection)을 권장하는 이유 (1) 2023.11.20 Early Return (0) 2023.11.18 Token인증 방식(JWT)과 Session인증 방식 (0) 2023.11.13 MVC 패턴 (0) 2023.11.12 - Bootstrap ClassLoader