JVM 게시글 두번째, 이번 게시글에서는 클래스로더를 통해 JVM의 Runtime Data Area에 올라온 바이트코드를 해석하여 실행하는 "Execution Engine"에 대해 알아볼 예정이며, 인터프리터와 JIT 컴파일러에 대해서는 게시글로 다뤘으므로 여기서는 자바의 가비지 컬렉션을 중심으로 알아볼 것이다.
실행 엔진은 클래스 로더를 통해 Runtime Data Area에 올라온 목적 파일(.class)을 각 운영체제가 이해할 수 있는 기계어로 변환하는 작업을 수행함.
이 수행 과정에서 실행 엔진은 인터프리터와 JIT 컴파일러 두 가지 방식을 혼합하여 바이트 코드를 실행.
바이트코드 명령어를 하나씩 읽어서 바로 실행함.
JVM 안에서 바이트코드는 기본적으로 인터프리터 방식으로 동작
하지만, 같은 메소드의 반복적인 호출이나, 매우 많이 도는 루프문의 경우 매번 해석하고 수행해야 돼서 전체적인 수행속도는 느림
위의 인터프리터의 단점을 보완하기 위해 도입된 방식으로 반복되는 코드를 발견하여 해당하는 바이트코드 부분을 컴파일하여 Native Code로 변경하고 캐싱하여 이후에는 해당 메서드를 더 이상 인터프리팅하지 않고, 캐싱된 것을 직접 실행함.
하지만, 매번 컴파일을 수행하는 것이 아닌, 특정 기준을 넘어가면 JIT 컴파일 방식으로 명령어를 실행함
인터프리터와 JIT 컴파일러 관련 세부 내용은 컴파일러와 인터프리터 그리고 JIT 컴파일러 게시글 참조
JVM은 가비지 컬렉터를 이용하여 Heap 메모리 영역에서 더는 사용하지 않는 메모리를 자동으로 회수해준다.
C의 경우 개발자가 직접 메모리를 해제해야하지만, Java는 자동으로 메모리 헤제가 수행되므로 프로그래밍이 더욱 손쉬워짐.
일반적으로는 자동으로 실행되지만(가비지 컬렉터가 실행되는 시간은 정해져 있지 않음), Full GC가 발생하는 경우 가비지 컬렉터를 제외한 모든 스레드가 중지되기 때문에 실행시간에 큰 지연이 생길 수 있음.

가비지 컬렉션은 특정 객체가 가비지인지 아닌지 판단하기 위해 도달성 이라는 개념을 적용한다.
객체에 참조가 있다면 Reachable로 구분되고, 객체에 유효한 참조가 없다면 Unreachable로 구분해 버리고 수거해버린다.
JVM 메모리에서는 객체들의 실제 데이터는 힙 영역에 저장되며, 이들을 메소드 영역이나 스택 영역에서 주소만 참조하는 형태로 구성된다.
이렇게 생성된 힙 영역의 객체들이 메서드가 끝나는 등의 특정 이벤트들로 인하여 힙 영역 객체의 메모리 주소를 가지고 있는 참조 변수가 삭제되는 현상이 발생하면, 결국에는 참조되고 있지 않은. 즉, Unreachable한 객체가 발생하게 된다.
이러한 객체들을 주기적으로 가비지 컬렉터가 제거해주는 것이다.
GC가 어떻게 Unreachable한 객체를 판별하고 청소하는지 알아보자.
가비지 컬렉션 대상이 될 객체를 식별(Mark)하고 제거(Sweep)하며, 객체가 제거되며 파편화된 메모리 영역을 앞에서부터 채워나가는 작업(Compaction)까지 수행하게 된다.
Mark 과정: 먼저 “Root Space”로부터 그래프 순회를 통해 연결된 객체들을 찾아내어 각각 어떤 객체를 참조하고 있는지 찾아서 마킹한다.
[ GC의 Root Space ]
JVM의 Root Space는 힙 메모리 영역을 참조하는 Method Area, Stack, Native Method Stack이 된다.
Root Space는 아래 그림처럼 여러개가 될 수 있음
Sweep 과정: 참조하고 있지 않은 객체(Unreachable 객체)들을 힙 영역에서 제거한다.
Compact 과정: Sweep 후 분산된 객체들을 힙의 시작 주소로 모아 메모리가 할당된 부분과 그렇지 않은 부분으로 압축한다. (가비지 컬렉터 종류에 따라 안하는 경우도 있음)
위 과정을 통해 루트로부터 연결이 끊긴 객체들을 모두 지울 수 있다.
JVM의 힙 영역은 동적으로 레퍼런스 타입의 데이터가 저장되는 공간으로, 가비지 컬렉션의 대상이 되는 공간이다.
힙 영역은 다음의 2가지 전제를 바탕으로 설계됨
즉, 객체는 대부분 일회성이며, 메모리에 오랫동안 남아 있는 경우는 드물다는 것이다.
(이러한 전제는 파이썬의 GC 방식에도 깔려있음, 파이썬 세대별 GC)
이러한 특성을 이용해 JVM 개발자들은 보다 효율적인 메모리 관리를 위해, 객체의 생존 기간에 따라 물리적인 힙 영역을 나누어 “Young”과 “Old” 두가지 영역으로 설계하였다.


또한, Java 7까지는 힙 영역에 “Permanent”가 존재했지만, Java 8부터는 Native Method Stack에 편입됨
[Parmanent]
Parmanent는 클래스 로더에 의해 로드되는 클래스, 메소드 등에 대한 메타 정보가 저장되는 영역

Young Generation 영역에서 일어나는 가비지 컬렉션.
Young Generation의 공간읜 Old에 비해 상대적으로 작기 때문에 메모리 상의 객체를 탐색하여 제거하는 시간이 비교적 적기 때문에 Minor GC라 부른다.
처음 생성된 객체는 Eden 영역에 위치
객체가 계속 생성되어 Eden 영역이 꽉차게 되고 Minor GC가 실행됨
[age 값이란]
Survivor 영역에서의 객체가 살아남은 횟수를 의미하는 값이며, Object Header에 기록됨
만일 age 값이 임계값에 다다르면 Promotion(Old 영역으로 이동) 여부를 결정함
가장 일반적인 HotSpot JVM의 경우 객체 헤더에 age를 기록하는 부분이 6 bit로 되어 있기 때문에 age의 임계값은 31이다.
또 다시 Eden 영역에 신규 객체들로 가득 차게 되면 다시 한번 Minor GC가 발생
위의 과정들을 반복

Major GC는 Old Generation 영역의 객체들에 대해 GC를 수행하는 것
Old Generation 객체들은 위에서 언급했듯이 age의 임계값에 도달하여 Promotion된 객체들이라고 할 수 있다.
이러한 객체들은 계속해서 살아남을 가능성이 높으므로 메모리에 계속 쌓여갈 것이기 때문에 Old Generation 영역의 메모리 크기는 Young보다 더 크다.
따라서, Major GC의 실행 속도는 느리다. 보통 Minor GC보다 10배 이상의 시간을 사용한다.
이는, 프로그램을 실행하면서 생기는 성능저하의 큰 원인이 됨.
JVM이 메모리를 자동으로 관리해준다는 점은 개발하는 입장에서는 굉장한 이점이나, GC를 수행하기 위해 실행 중인 모든 쓰레드를 중단하고 GC를 수행하는 “Stop-The-World”는 애플리케이션이 중지되는 문제를 야기한다.
이러한 점 때문에, 실행 시간 지연을 최소화하고자 다양한 가비지 컬렉션 알고리즘이 개발되었다.
다음의 GC 알고리즘들은 모두 설정을 통해 Java에 적용할 수 있는 알고리즘으로 상황에 따라 필요한 GC 방식을 설정하여 사용할 수 있다.
java -XX:+UseSerialGC -jar Application.java
java -XX:+UseParallelGC -jar Application.java
// 사용할 쓰레드의 갯수
-XX:ParallelGCThreads=<N>
// 최대 지연 시간
-XX:MaxGCPauseMillis=<N>
java -XX:+UseParallelOldGC -jar Application.java
// 사용할 쓰레드의 갯수
-XX:ParallelGCThreads=N :

// deprecated in java9 and finally dropped in java14
java -XX:+UseConcMarkSweepGC -jar Application.java

java -XX:+UseG1GC -jar Application.java
한 Region에 객체를 할당하다가 해당 Region이 가득 차면 다른 Region에 객체를 할당하고 Minor GC가 실행됨
G1 GC는 각 Region을 추적하고 있기 때문에, 가비지가 가장 많은(Garbage First) Region을 찾아서 Mark and Sweep을 수행함.
Eden 지역에서 GC가 수행되면 살아남은 객체를 Mark하고 메모리를 Sweep함.
그리고, 살아남은 객체를 다른 지역으로 이동시키며, 복제되는 지역이 Available/Unused Region이면 이제 Survivor Region이 되고, Eden Region은 Available/Unused Region이 됨
시스템이 계속 운영되다가 객체가 너무 많아 빠르게 메모리 회수를 할 수 없을 때 Major GC가 수행됨
여기서 G1 GC는 다른 GC들과 큰 차이점을 보임

java -XX:+UseShenandoahGC -jar Application.java

java -XX:+UnlockExperimentalVMOptions -XX:+UseZGC -jar Application.java
https://velog.io/@yarogono/Java가비지-컬렉터Garbage-Collector란
https://inpa.tistory.com/entry/JAVA-☕-JVM-내부-구조-메모리-영역-심화편
https://inpa.tistory.com/entry/JAVA-☕-가비지-컬렉션GC-동작-원리-알고리즘-💯-총정리