JVM에 대하여 간단하게 설명하자면 여러 OS에서 java code의 동일한 동작으로 보장해주는 가상 머신이다. JVM은 Java Virtual Machine의 약자로, 자바 가상 머신이다.
JVM은 두 가지 기본 기능이 있다. 방금 언급했던 어느 운영체제에서도 실행될 수 있게 하는 것(Java의 한 번 작성해, 어디에서나 실행 원칙), 그리고 메모리 관리이다. Java의 등장 전까지는 메모리를 개발자가 관리했으나 Java는 JVM의 Garbage Collection을 통해 메모리 관리를 자기가 “알아서” 한다.
바이트 코드 - 가상머신이 기계어와 같이 효율적으로 저장하고 불러올 수 있는 형식
자바는 동적 로드, 즉 컴파일 타임이 아니라 런타임(바이트 코드를 실행할 때)에 클래스를 로드하고 링크하는 특징이 있다. 이 동적 로드를 담당하는 부분이 JVM의 클래스 로더이다.
jar 파일 내 저장된 클래스들을 JVM위에 탑재하고 사용하지 않는 클래스들은 메모리에서 삭제한다.
자바는 컴파일 타임이 아니라 런타임에 참조한다. 즉 클래스를 처음으로 참조할 때, 해당 클래스를 로드하고 링크한다.
클래스 로더에는 로딩, 링크, 초기화 단계로 나뉘어져 있다.
클래스를 실행시키는 역할이다. 클래스 로더가 JVM내의 런타임 데이터 영역에 바이트 코드를 배치시키고, 이것을 실행엔진이 해석, 실행한다. 메모리에 적재된 바이트 코드를 기계어로 변경하여 실행하는 것이다. Execution engine이 바이트 코드를 기계어 명령어 단위로 읽어서 수행하는데 두 가지 방식이 사용된다.
Execution engine은 자바 바이트 코드를 명령어 다뉘로 읽어서 실행한다. 하지만 이 방식은 인터프리터 언어의 단점을 그대로 가지고 있다. 한 줄 씩 수행하기에 느리다. 당연히 컴파일 언어보다 느리다.
의문점 - 컴파일 최적화는 언제?
⇒ 바이트 코드로 컴파일 할 때는 거의 최적화 안한다
JIT에서 최적화를 한다.
인터프리터의 속도 이슈를 해결하기 위한 방법
자주 실행되는 바이트 코드 영역을 런타임 중에 기계어로 컴파일하여 사용
이후에는 더이상 해당 코드를 인터프리팅 하지 않고 기계어(네이티브 코드)로 직접 실행한다.
네이티브 코드는 캐시에 보관되어 한번 컴파일된 코드는 빠르게 수행할 수 있다.
GC를 수행한다
JVM이 운영체제 위해 실행될 때, 운영체제로부터 할당 받은 메모리 영역이며, 총 6개의 메모리 영역으로 분할하여 관리한다. 이 영역들은 스레드가 공유하는 공간인지 아닌지로 나눈다.
스레드마다 생성되는 공간
- PC 레지스터
- JVM 스택
- 네이티브 메소드 스택
모든 스레드가 공유하는 공간
- 힙
- 메소드
- 런타임 상수 풀
스레드가 시작될 때 생성된다. 메소드 안에서 바이트 코드 몇 번째 줄을 실행하고 있는지와 같은 정보를 가지고 있다. 현재 수행 중인 JVM 명령의 주소를 가지고 있는 것이다.
PC 레지스터에는 현재 실행 중인 메서드가
스택 프레임이라는 구조체를 JVM 스택에 쌓는다. 기존 process call stack 처럼 새로운 메소드가 호출 될 때마다 push, 끝나면 pop 동작을 수행한다. 프로그램 실행과정에서 임시로 할당되었다가 메소드를 빠져나가면 사라지는 영역이다.
스레드가 쓸 수 있는 스택의 사이즈를 넘기게 되면 StackOverflowError
가 발생한다.
스택 사이즈를 동적으로 확장할 수도 있는데 확장할 메모리가 부족하거나, 새로운 스레드를 만들 때 필요한 새로운 스택에 할당할 메모리가 부족하면 OutOfMemoryError
가 발생한다.
스택 프레임은 다음 세 가지로 구성
Local Variable Array
메소드의 로컬 변수들을 배열로 가지고 있다
int
나, double
이 Integer
, Double
보다 조금 더 빠르다.double
, long
은 두 칸씩 차지한다.Operand Stack
오퍼랜드 스택은 메소드 내 계산을 위한 작업 공간이다. 스택 기반의 가상 머신이기에 사용하는 스택 공간이다. 예시는 링크 참조
Constant Pool Reference
프레임은 런타임 상수 풀의 참조를 가진다. 상수는 상수 풀에서 참조해서 사용한다.
참고) String을 new해서 쓰지 않는 이유
JVM stack은 실제 OS process에서 stack 메모리일까 heap 메모리일까?
JVM stack은 인터페이스일뿐, 실제 OS에서 어디에든 할당 할 수 있다
네이티브 메소드 스택은 자바 바이트 코드가 아닌 다른 언어로 작성된 네이티브 코드를 위한 스택이다. 성능 향상을 목적으로 작성되었다.
JVM 스택과 네이티브 스택이 나뉘어져 있다 하더라도 자바 코드를 수행하다 JNI(Java Native Interface)를 호출하면, JVM 스택에서 네이티브 메소드 스택으로 다이나믹 링킹을 통해 확장할 뿐이다.
네이티브 메소드가 네비티브 메소드 스택(OS에서 관리되는 스택)을 잡아 독자적으로 프로그램을 실행시키는 영역이다. 이 부분을 통해 C 코드를 실행시켜 커널에 접근할 수 있다.
모든 스레드가 공유하며, 런타임시 할당된다. new 키워드로 생성된 객체와 배열이 생성되는 영역이다. 또한, 메소드 영역에 로드된 클래스만 생성이 가능하고 Garbage Collector가 참조되지 않는 메모리를 확인하고 제거하는 영역이다.
Java 8부터 Permanent Generation 메모리 영역이 없어지고 Metaspace 영역이 생겼다.
JVM이 시작될 때 생성된다. 클래스 정보를 처음 메모리 공간에 올릴 때 초기화되는 대상을 저장하기 위한 메모리 공간이다.
클래스 로더가 클래스 파일을 읽어 오면, 클래스 정보를 파싱하여 런타임 상수 풀, 필드와 메소드 정보, static 변수, 인터페이스, 메소드의 바이트 코드 등을 보관한다.
메소드 영역은 JVM 벤더마다 다양한 형태로 구현할 수 있으며, 오라클 핫스팟 JVM에서는 흔히 PermGen(자바 1.7 이전), MetaSpace(자바 1.8 이후)로 부른다.
메소드 영역에 대한 GC도 JVM 벤더의 선택 사항이다.
Java 8이전에 Method Area를 PermGen(Permanat Generation Space)에 할당 했다.
Java 8이후에는 PermGen이 완전히 제거 되어 Method Area는 Native Heap에 할당 된다. 과거의 PermGen은 현재 Metaspace 로 불리우는데 Method Area는 Meta 정보를 저장 하기 때문이다. - MIA_DAHAE
런타임 상수 풀은 메소드 영역에 포함되는 영역이긴 하지만, JVM 동작에서 가장 핵심적인 역할을 수행하는 곳이기 때문에 JVM 명세에서도 따로 중요하게 기술한다.
각 클래스와 인터페이스의 상수 뿐만 아니라, 메소드와 필드에 대한 모든 레퍼런스까지 담고 있는 테이블이다.
즉, 어떤 메소드나 필드를 참조할 때 JVM은 런타임 상수 풀을 통해 해당 메소드나 필드의 실제 메모리 상 주소를 찾아서 참조한다.
#자바가상머신, JVM(Java Virtual Machine)이란 무엇인가?
[Java] JVM이란?
JVM 이해하기 - 1 (JVM 특징 이해하기)
JVM 메모리 구조란? (JAVA)
Back to the Essence - Java 컴파일에서 실행까지