JVM and GC Flashcards
JVM
Java Virtual Machine 의 줄임말 이며 Java Byte Code를 OS에 맞게 해석 해주는 역할을 합니다.
Java compiler는 .java 파일을 .class 라는 Java byte code로 변환 시켜 줍니다.
Byte Code 는 기계어가 아니기 때문에OS에서 바로 실행되지 않습니다.
이때 JVM은 OS가 ByteCode를 이해할 수 있도록 해석 해줍니다.
하지만 JVM의 해석을 거치기 때문에 c언어 같은 네이티브 언어에비해 속도가 느렸지만 JIT(Just In Time)컴파일러를 구현해 이점을 극복했습니다.
Byte Code는 JVM 위에서 OS상관없이 실행된다. 이런 점이 Java의 가장 큰 장점이라고 할수 있습니다. OS에 종속적이지 않고 Java 파일 하나만 만들면 어느 디바이스든 JVM 위에서 실행 할 수 있습니다.
JVM은 크게 Class Loader, Runtime Data Areas, Excution Engine 3가지로 구성되어 있고 자세한 설명은 아래에 이이서 하겠습니다.
Class Loader
RunTime 시점에 클래스를 로딩하게 해주며 클래스의 인스턴스를 생성하면 클래스 로더를 통해 메모리에 로드하게 됩니다.
Runtime Data Areas
JVM이 프로그램을 수행하기 위해 OS로 부터 별도로 할당 받은 메모리 공간을 말하며, Runtime Data Areas는 크게 5가지 영역으로 나눌 수 있습니다.
Execution Engine
Load된 Class의 ByteCode를 실행하는 Runtime Module이 바로 Execution Engine입니다. Class Loader를 통해 JVM 내의 Runtime Data Areas 에 배치된 바이트 코드는 Executin Engine에 의해 실행되며, 실행 엔진은 자바 바이트 코드를 명령어 단위로 읽어서 실행합니다.
최초 JVM 이 나왔을 당시에는 Interperter방식(한 줄씩 해석하고 실행)이였기 때문에 속도가 느리다는 단점이 있었지만 JIT complier 방식을 통해 이 점을 보완했습니다. JIT는 ByteCode를 어셈블러 같은 NativeCode로 바꿔서 실행이 빠르지만 역시 변환하는데 비용이 발생합니다. 이 같은 이유 때문에 JVM은 모든 코드를 JIT Compiler 방식으로 실행하지 않고 Interpreter 방식을 사용하다 일정한 기준이 넘어가면 JIT Compiler 방식으로 실행합니다.
가비지 컬랙션
C/C++ 언어와 달리 자바는 개발자가 명시적으로 객체를 해제할 필요가 없습니다. 자바 언어의 큰 장점이기도 합니다. 사용하지 않는 객체는 메모리에서 삭제하는 작업을 Gargabe Collection(GC)라고 부르며 JVM에서 GC를 수행합니다. 기본적으로 JVM의 메모리는 총 5가지 영역(ex. 클래스, 스택, 힙, 네이티브 메서드, PC)으로 나뉘는데, GC는 힙 메모리만 다룹니다.
코드상에서 어떨 때 객체가 가비지 대상이 될까요? 간단하게 생각해보면 프로그램이 실행되면서 코드상에서 참조되지 않는 객체들이 대상이 될 것입니다. 일반적으로 아래 같은 경우에 가비지대상이 됩니다.
객체가 null인 경우 (ex. String str = null)
블럭 안에서 생성된 객체는 블럭 실행 종료후 대상이 된다
부모 객체가 null이 되면, 포함하는 자식 객체들도 자동으로 가비지 대상이 된다
1.1 Heap 영역의 구조
Heap 영역은 크게 2가지 영역으로 나뉩니다. Permanent Generation 영역은 Heap 영역은 아닙니다.
Young Generation - 객체 사용 시간이 짧은 객체들
영역의 종류
Eden
Survivor 2개
새롭게 생성한 객체는 여기에 위치한다
매우 많은 객체가 Young 영역에 생성되었다가 사라진다
이 영역에서 객체가 사라지면 Minor GC가 발생했다고 한다
Old Generation (Tenured space) - 오래 사용되는 객체들
Young 영역에서 살아남은 객체가 여기로 복사된다
Young 영역보다 크게 메모리가 크게 할당되어 Young 영역보다 GC는 적게 발생한다
이 영역에서 객체가 살아지면 Major GC (Full GC)가 발생했다고 한다
(Non-heap) Permanent Generation
이 영역에는 JVM에 의해서 사용하는 클래스와 메서드 객체 정보를 담고 있다
JDK8부터는 PermGen은 Metaspace로 교체된다
Heap 영역을 왜 두 가지 영역으로 나뉘서 관리하게 되었을까요
Heap 영역을 왜 두 가지 영역으로 나뉘서 관리하게 되었을까요? 여러 연구를 진행한 결과 애플리케이션에서 객체가 생성되고 살아지는 패턴은 크게 2가지 특징을 가지게 된다고 합니다.
대부분의 생성된 객체는 금방 사용하지 않는다
객체들은 대개 (아주) 오랜 시간 동안 남아 있지 않는 것들이다 (객체 짧게 사용됨)
아래 그래프에서 보이는 것처럼 객체의 라이프는 짧게 사용되다가 오랫동안 남은 것들은 계속 쌓이게 되는 것을 볼 수 있습니다. 이런 특징으로 두 영역으로 나뉘어서 관리하고 GC 알고리즘도 이 기반으로 설계되었습니다.
- Garbage Collection 알고리즘 G1GC
G1 (Garbage First) collector는 메모리가 큰 multi core 머신을 타켓으로 설계되었습니다. G1 GC는 JDK7u4부터 도입 되었고 안정화 기간 거쳐 현재 JDK9에서는 기본 GC로 채택 되었습니다. G1에서는 아래 그림과 같이 heap 메모리 영역을 작은 단위의 region으로 나눠서 관리합니다. 기본 region 개수 수치는 2K(2048)개 공간으로 나눕니다. 예를 들면 Heap Size가 8GB로 지정하면, 각 region의 크기는 4MB (ex. 8192MB/2048 = 4096)가 됩니다.
Young 영역 (multi thread)
-XX:ParallelGCThreads로 thread 갯수를 조정할 수 있다
살아 남은 객체들은 survivor region으로 이동(evacuation/compacting)한다
정의된 aging threshold 값을 넘으면, survivor region의 오래된 객체는 Old 영역 region으로 이동 한다
매번 Minor GC를 수행할때마다 Eden과 Survivor 영역 크기는 자동으로 계산하여 정해진다
Old 영역 (multi thread)
-XX:ConcGCThreads로 marking 단계에 사용되는 GC 쓰레드 갯수 조정 가능하다
전체 heap에 대해서 GC를 하지 않고 일부 region에서만 GC 를 수행한다
Old region 영역의 GC 선택 기준은 liveness(살아 있는 객체/사용하는 객체)를 기준 으로 판단한다
GC 효율을 높이기 위해 liveness가 높은 것은 재사용 될 가능성이 높다고 판단하기에 liveness가 적은 것을 GC하도록 한다. 따라서 Garbage First, G1이라는 이름 이 붙었다
GC하는 과정
initial mark (stop-the-world)
Old GC가 필요해지면 Young GC때 함께 실행된다
survivor region(root region)에서 Old 영역에 있는 객체를 참조하는 survivor 영역을 표시한다
root region scanning
어플리케이션 실행중단없이 첫번째 단계에서 표시한 survivor 영역을 스캔한다
concurrent mark
전체 heap 영역에서 사용하는 객체를 표시한다
이 단계에서 young GC가 발생하면 멈출수도 있다
region별 live object 비율(재사용이 높은 값)을 계산해둔다
remark (stop-the-world)
빈 region들은 삭제(객체를 이동하면서 빈 region이 생김)해서 free로 만든다
전체 region들의 live object 비율이 계산된다
copy/cleanup (stop-the-world)
가장 빨리 청소가 가능한 live object 비율이 낮은 region들을 선택한다
Young과 Old 영역이 모두 cleanup되고 선택된 region들은 모두 새로운 region으로 compaction되어 위치한다