Java공부를 하면 JVM 이란 단어를 많이 들어보고 공부해 봤을 것이다.
이번 기회에 JVM에 대해 자세히 알아보자.
JVM이란?
-Java Virtual Machine
- 자바를 실행시키기 위한 가상 기계라고 해석할 수 있다.
- 사용자의 OS에 종속받지 않고 OS 위에서 가상 기계를 통해 자바를 실행시킨다. 따라서 자바는 사용자의 OS 종류에 관계없이 자바를 실행할 수 있다.
- 구성 : 자바 인터프리터, JIT , 클래스 로더, Runtime Data Area, 가비지 컬렉터로 되어있다.
JIT & Interpreter
위의 그림을 보게 되면 java 파일을 컴파일러를 통해 .class 파일로 변환한 뒤 JVM이. class 파일을 받아 바이트 코드의 명령에 따라 내재된 기능을 수행한다.
Java compiler
Java compiler는 .java 파일을. class 파일(java byte code)로 변환해 준다.
javac Application.java
JDK가 설치되어 있는 상황이라면 JDK 내부에 있는 javac.exe 파일에 의해 .java 파일을. class 파일로 변환해 준다.
- 바이트 코드란?
가상 머신이 인식하기 쉬운 코드, 즉 자바 가상머신이 사용하는 코드
바이트 코드를 받은 JVM은 바이트 코드를 해석하기 위해 컴파일과 인터프리터 둘 다 진행한다.
- 컴파일 방식 : 소스코드를 한꺼번에 컴퓨터가 읽을 수 있는 기계어로 변환
- 인터프리터 방식 : 소스코드를 빌드 시에 아무것도 하지 않고, 런타임시에 한 줄 한 줄 읽어가며 기계어로 변환
여기서 JVM은 속도를 높이기 위해 JIT Compiler를 사용하게 된다.
이미 한 번 읽어서 기계어로 변경한 소스코드는 번역하지 않는다. ( 자주 쓰이는 loop 문 등...)
이를 사진으로 보게 되면
처음 바이트코드를 인터프리터가 읽을 때 자주 쓰이는 코드를 JIT Compiler에 담아 두어 인터프리터가 해석할 때 JIT Compiler에 저장되어 있는 바이트 코드는 해석하지 않고 바로 해석된 결과를 얻을 수 있다.
이를 통해 Java File을 실행시킬 수 있다.
지금까지 자바 파일이 실행되는 과정과 JIT Compiler가 어떻게 사용되는지 알아보았다.
Class Loader
JVM 내로 클래스 파일 ( *. class)을 로드하고, 링크를 통해 초기화하는 작업을 진행합니다.
초기화 작업 시에 static field의 값들을 정의한 값으로 초기화
런타임 시 동적으로 클래스를 로드하고 jar 파일 내 저장된 클래스들을 JVM 위에 탑재한다.
즉, 클래스를 처음 참조 할 때 해당 클래스를 로드하고 링크하는 역할을 한다.
Execution Engine
클래스를 실행시키는 역할
클래스로더가 JVM내의 런타임 데이터 영역에 바이트 코드를 배치시키고 실행엔진에 의해 실행됩니다.
실행엔진은 자바 바이트 코드를 명령어 단위로 읽어 실행한다. 이때 Interpreter 방식과 JIT Compiler 방식을 사용한다.
Runtime Data Area
Runtime Data Area는 JVM이 프로그램을 수행하기 위해 OS로부터 별도로 할당받은 메모리 공간을 말한다.
1. PC Register
JVM의 PC Register는 CPU Register와 다르게 각 스레드 별로 하나씩 존재하며 현재 수행 중인 JVM Instruction을 가지게 된다.
스레드가 어떤 명령을 실행할지 기록하는 부분
2. JVM Stack
메서드가 호출될 때 메서드와 메서드의 정보는 Stack에 쌓이게 된다. 메서드의 매개변수, 지역변수, return 주소, 임시 변수등의 정보를 기록하는 스택이다. 각 스레드별로 생기기 때문에 다른 스레드는 접근할 수 없다. 메서드 호출이 종료되면 스택의 정보들이 제거된다.
3. Native Method Stack
자바 외의 언어로 작성된 Native code들을 위한 스택. C/C++ 등이 존재
4. Method Area
모든 메서드가 공유하는 메모리 영역, 클래스 인터페이스 메서드 필드 static 변수 등 바이트 코드를 보관한다.
Runtime Constant Pool이라는 별도의 관리 영역이 존재한다. 상수 자료형을 저장하고 참고하여 중복을 막는 역할 수행
5. Heap
객체를 저장하는 가상메모리 공간. new 연산자로 생성되는 객체와 배열을 저장한다.
Class Area (Static area)에 올라온 클래스들만 객체로 생성할 수 있다.
- Permanent Generation
생성된 객체들의 정보의 주소값이 저장된 공간. 클래스 로더에 의해 load 되는 class, method에 대한 meta 정보가 저장되는 영역, JVM에 의해 사용된다.
Reflection을 사용하여 동적으로 클래스가 로딩되는 경우에 사용된다.
Reflection이란 객체를 통해 클래스의 정보를 분석해 해는 프로그래밍 기법. 구체적인 클래스 타입을 알지 못해도, 컴파일된 바이트 코드를 통해 역으로 클래스의 정보를 알아내어 사용할 수 있다.
- New/Young Generation
이 영역의 인스턴스들은 추후 가비지 컬렉터에 의해 사라진다.
생명 주기가 짧은 젊은 객체를 GC 대상으로 하는 영역이다.
여기서 일어나는 가비지 컬렉트를 Minor GC라고 한다.
Eden : 객체들이 최초로 생성되는 공간
Survivor 0,1 : Eden에서 참조되는 객체들이 저장되는 공간
Eden 영역에 객체가 가득 차게 되면 첫 번째 GC가 발생한다. Eden 영역에 있는 값들을 Survivor 영역에 복사하고 이 영역을 제외한 나머지 객체들을 삭제한다.
- Old
이 영역의 인스턴스들은 추후 GC에 의해 사라진다.
생명 주기가 긴 오래된 객체를 GC 대상으로 하는 영역이다
여기서 일어나는 GC를 Major GC라고 한다. Minor GC에 비해 속도가 느리다.
New/Young Area에서 일정시간 참조되고 있는, 살아남은 객체들이 저장되는 공간이다.
Thread 별로 메모리를 할당받는 Java Stack과 달리 속도가 느리다.
모든 Thread들이 해당 영역인 Heap을 공유하여 Java 동시성 문제가 발생하게 된다.
각각의 Thread 메모리 관리 되는 것과 달리 Thread에 의해서 공유가 되기 때문에 Thread safe 하지 않다.
synchronized를 사용하여 동시성을 지켜주는 방법을 사용해야 한다.
Garbage Collector
더 이상 참조되지 않는 Garbage라고 불리는 불필요한 메모리를 알아서 정리해 주는 역할을 한다.
주로 동작하는 대상 : Heap 영역 내의 객체 중 참조되지 않은 데이터
Stop - the - world
stop the world 란 GC를 실행시키기 위해 JVM이 애플리케이션 실행을 멈추는 것이다. stop the world가 발생하면 GC를 실행하는 스레드를 제외한 나머지 스레드는 모두 작업을 멈춘다. GC 작업을 완료한 이후에야 중단했던 작업을 다시 시작한다.
JVM의 Heap 영역은 Weak Generational Hypothesis란 두 가지 가설을 전제로 설계되었다.
1. 대부분의 객체는 금방 접근 불가능한 (Unreachable)가 된다.
2. 오래된 객체에서 새로운 객체로의 참조는 아주 적게 존재한다.
이러한 가설의 장점을 살리기 위해 heap 영역엔 두 개의 영역이 존재한다.
- Young 영역(Young Generation)
- 새롭게 생성된 객체가 할당되는 영역
- 대부분의 객체가 금방 Unreachable 상태가 되므로 많은 객체가 Young영역에 생성되었다 사라진다.
- Young영역에 대한 GC를 Minor GC라고 부른다.
- Old 영역(Old Generation)
- Young 영역에서 Reachable 상태를 유지하여 살아남은 객체가 복사되는 영역
- 복사되는 과정에서 대부분 Young 영역보다 크게 할당되며, 크기가 큰 만큼 가비지는 적게 발생한다.
- Old 영역에 대한 GC를 Major GC 또는 Full GC라고 부른다.
Mark and Sweep Algorithm
Root set으로부터 출발하여 참조되는 객체들에 대해서 Mark를 하게 된다. -> Mark Phase
이후 마크되지 않은 객체들을 추적하여 삭제 -> Sweep Phase
메모리가 Fragmentation , 단편화되는 단점이 있다.
정렬되지 않은 조각으로 나뉘어 추가적인 메모리 할당이 되기 힘든 상태이다.
이러한 문제점을 해결하기 위한 것이 mark and compact algorithm
mark and compact algorithm
빈 공간들을 없애고, 사용되는 메모리들을 연속적으로 붙여준다.
Minor GC
Young 영역엔 3가지 영역(Eden, Survivor0,1 영역)이 존재한다.
- Eden 영역 : 새로 생성된 객체가 할당되는 영역
- Survivor 0,1 영역 : 최소 1번의 GC 이후 살아남은 객체가 존재하는 영역
Minor GC는 Young 영역에서 일어나는 GC이다. Minor GC의 과정은 다음과 같다.
1. 새로 생성된 객체가 Eden 영역에 할당된다.
2. Eden 영역이 가득 차게 되면 Minor GC가 발생한다.
1) Eden 영역에서 사용되지 않는(=참조되지 않는) 객체의 메모리가 해제된다.
2) Eden 영역에서 살아남은(=해제되지 않은) 객체는 Survivor 0,1 영역 중 하나로 이동한다.
3. 위 과정이 반복되다 Survivor 영역이 가득 차면 Survivor영역의 살아남은 객체를 다른 Survivor 영역으로 이동시킨다.
4. 위 과정을 반복하여 계속해서 살아남은 객체는 Old 영역으로 이동된다.
3번 과정에서 주의할 점은 Survivor 영역 중 하나는 반드시 비어있는 상태로 남아 있어야 한다는 것이다. 만약 두 Survivor 영역에 모두 데이터가 존재하거나, 두 영역 모두 사용량이 0이라면 시스템이 정상적인 상황이 아닌 것이다.
위의 그림에서는 Survivor0에서 Survivor1로 이동하는 것을 예시로 들었을 뿐, 무조건 0에서 1로 이동하는 것은 아니다.
Major GC
Major GC는 Old 영역이 데이터가 가득 차면 발생한다. 보통 Old 영역은 Young 영역보다 크게 할당하며, 이러한 이유로 Old 영역의 GC는 Young영역보다 적게 발생한다. 또한 크기가 크기 때문에 Minor GC보다 시간이 오래 걸린다.
즉, Major GC는 Old 영역의 더 이상 사용되지 않는 객체의 메모리가 해제되는 과정이다.
'자바' 카테고리의 다른 글
동시성 제어하기 (0) | 2023.10.05 |
---|---|
스트림 (0) | 2023.01.27 |
람다 표현식 (1) | 2023.01.11 |
동적 파라미터화 (0) | 2023.01.10 |
Java List 와 배열 (1) | 2023.01.03 |