JVM/자바 코드 실행 원리
- JVM(Java Virtual Machine)

- 자바 어플리케이션을 어느 CPU나 OS에서도 실행할 수 있게 지원하는 역할을 수행
- 자바 코드를 컴파일하여 바이트 코드로 변환하여 해당 운영체제(OS)가 이해할 수 있는 기계어로 실행
- 구성
- Class Loader
- Execution Engine
- Garbage Collector
- Runtime Data Area
- 자바 어플리케이션 실행 과정
- 어플리케이션이 실행되면 JVM이 OS로부터 메모리를 할당 받음
- JVM은 할당 받은 메모리를 용도에 따라 영역을 구분하여 관리
- 자바 컴파일러(javac.exe)가 자바 소스코드(.java)를 읽어 바이트 코드(.class)로 변환
- Class Loader를 통해 바이트 코드를 JVM으로 로딩
- 로딩된 바이트 코드(.class)는 Execution Engine을 통해 해석됨
- 해석된 바이트 코드는 Runtime Data Areas에 배치되어 실행됨
- Runtime Data Areas가 실질적인 메모리를 할당 받아 관리를 하게 되는 영역
- 실행되는 과정에서 GC(Garbage Collector) 같은 작업이 수행됨
- Execution Engine

- Runtime Data Areas에 할당된 바이트 코드를 실행시키는 주체
- 코드를 실행하는 방식
-
Interpreter
- 바이트 코드를 해석하여 실행하는 역할
- 같은 메소드라도 여러번 호출될 때 매번 새로 수행해야 함
-
JIT(Just In Time) Compiler
- 반복되는 코드를 발견하여 전체 바이트 코드를 컴파일하고 그것을 Native Code로 변경하여 사용
Note. Native : 자바에서 부모가 되는 C 언어나 C++, 어셈블리어를 의미
-
Garbage Collector
- 더이상 참조되지 않는 메모리 객체를 모아 제거하는 역할을 수행
- 일반적으로 자동으로 실행되지만, 수동으로 실행하기 위해 ‘System.gc()’를 사용할 수 있음(실행이 보장되지는 않음)
JVM을 공부해야 하는 이유?
- Garbage Collection을 이해하기 위해; 자바의 특징인 메모리를 자동으로 관리해준다는 부분에서 ‘퍼포먼스 튜닝’이라는 것을 하는데, 이 부분에서 가장 큰 파이를 차지하는 것이 Garbage Collection(GC)에 대한 설정값들을 변경해주는 작업
- Garbage Collector
- 앞으로 사용되지 않는 객체의 메모리를 Garbage라고 부르고, 이런 Garbage를 정해진 스케줄에 의해 정리해주는 것을 Garbage Collection(GC)이라 부름
- GC는 Heap 메모리에서 활동하며, JVM에서 GC의 스케줄링과 개발자가 직접 관여하지 않아도 더 이상 사용하지 않는 점유된 메모리를 제거해주는 역할을 담당
- Stop The World(STW)
-
GC를 수행하기 위해 JVM이 멈추는 현상
-
GC가 작동하는 동안 GC관련 Thread를 제외한 모든 Thread는 멈춤(어플리케이션이 멈춰 있는 상태)
Thread? 프로세스(process, 사용자가 작성한 프로그램이 OS에 의해 메모리를 할당 받아 실행 중인 것) 내에서 실제로 작업을 수행하는 주체. 모든 프로세스에는 한 개 이상의 스레드가 존재하여 작업을 수행함
-
일반적으로 ‘퍼포먼스 튜닝’이라는 것은 STW 시간을 최소화하는 것을 의미함
퍼포먼스 튜닝 : GC가 어떤 방식으로 동작하는지와 어떠한 경우에 발생하는지를 체크하고, 주어진 어플리케이션에 맞는 설정값을 넣는 것
- GC의 종류
- Serial GC
- Parallel GC(자바 8의 default GC)
- CMS GC
- G1 GC (자바 9, 10의 default GC)
- Z GC (자바 11의 default GC, 레퍼런스가 많지 않음)
- 어떤 GC가 좋다고 할 수는 없고, 주어진 어플리케이션, 서비스 특성에 맞는 GC를 선택하여 사용하는 것이 좋다
- Class Loader

- JVM으로 바이트 코드(.class)를 로드하고, 링크를 통해 배치하는 작업을 수행하는 모듈
- 로드된 바이트 코드들을 엮어서 JVM의 메모리 영역인 Runtime Data Areas에 배치함
- 클래스를 메모리에 올리는 로딩 기능은 한번에 메모리에 올리지 않고, 어플리케이션에서 필요한 경우 동적으로 메모리에 적재하게 됨 : 클래스 내의 멤버를 호출하게 되면 로드한다
- 클래스 파일의 로딩은 3단계로 구성됨 : Loading → Linking → Initialization
- Runtime Data Area

Heap Area을 제대로 알아야 메모리 관리를 어떻게 최적화 할 수 있는지를 알 수 있고, GC가 어떻게 동작하는지도 알 수 있다.
- GC의 원리
- 약한 세대 가설(weak generational hypothesis)
- 대부분의 객체는 금방 접근 불가능(Unreachable)한 상태가 된다.
- 대부분의 객체는 중괄호({}) 안에서 생성되며, 이 객체들은 괄호가 끝나는 시점에서 더 이상 사용되지 않는다.
- 특수한 경우에는 오래 사용할 수 있지만, 대부분의 경우 Unreachable한 상태가 되어 GC의 대상이 된다.
- 오래된 객체에서 젊은 객체로의 참조는 아주 적게 발생한다.
- 순차적인 로직에 의해 객체를 생성하여 활용하는데, 앞에 생성된 객체는 그다음의 로직에서 사용된 이후 대부분 사용되지 않게 된다.
- 위의 가정을 기반으로 메모리 구조를 크게 2개로 나눈다.
- Young Generation : Eden, S0, S1
- Old Generation
- GC Algorithm
- Reference Counting Algorithm
- Garbage 탐색에 초점을 맞춘 알고리즘
- 각 객체마다 Reference Count를 관리하며, 이 카운트가 0이 되면 GC를 수행한다.(바로 메모리에서 제거된다.)
- 순환 참조 구조에서 Reference Count가 0이 되지 않는 문제가 발생하여 Memory Leak이 발생할 수 있다.(Memory Leak : 프로그램이 필요하지 않은 메모리를 계속 점유하고 있는 것)
- Mark-and-Sweep Algorithm
- Root Set에서 Reference를 추적하여 참조 상황을 파악하고, Mark 단계에서 Garbage 대상이 아닌 객체를 마킹한다.
- Sweep 단계에서 마킹되지 않은 객체를 지우는 작업을 수행하고 마킹 정보를 초기화한다.
- GC가 동작하고 있을 경우, Mark 작업과 어플리케이션 Thread의 충돌을 방지하기 위해 Heap 사용이 제한된다.
- Compaction 작업(디스크 조각 모음 같은)이 없어서 비어 있는 공간이 충분하지 않으면 Out of Memory가 발생할 수 있다.
- Mark-and-Compact Algorithm
- 위의 알고리즘에서 Compact 작업이 추가되어 흩어져 있는 메모리를 모아주는 작업을 진행한다.
- 메모리 효율을 높일 수는 있지만, Compact 작업과 Reference를 업데이트하는 작업 때문에 오버헤드(Overhead)가 발생할 수 있다.
- Copying Algorithm
- Concurrent Mark-Sweep
- Generational Algorithm
- 일반적인 GC 과정 : 각 영역을 활용하여 최적의메모리 운영을 하게 됨
- 맨 처음 객체가 생성되면 Eden 영역에 생성
- Minor GC가 발생하면 미사용 객체의 제거와 함께 아직 사용되고 있는 객체는 Survivor0, Survivor1 영역으로 이동시킴
단, 객체의 크기가 Survivor 영역의 크기보다 클 경우에는 바로 Old Generation으로 이동
- Survivor0과 Survivor1 영역은 둘 중 한 곳에만 객체가 존재하게끔 운영되며, 다른 한 곳은 비어 있어야 한다.
객체가 존재하는 Survivor 영역(From)이 가득 차면 다른 Survivor 영역(To)로 보내고, 기존의 Survivor 영역(From)을 비우는 작업을 진행
- 1~3번 과정을 반복하면서 Survivor 영역에서 계속 살아남은 객체들에게 일정 score가 누적이 되어 기준치 이상이 되면 Old Generation 영역으로 이동하게 된다.(Promotion)
- Old Generation 영역에서 살아남았던 객체들이 일정 수준 쌓이게 되면, 미사용으로 판단된 객체들을 제거해주는 Full GC가 발생하고, 이 과정에서 STW가 발생한다.
- GC 종류
- Serial GC
- 하나의 CPU로 Young Generation과 Old Generation을 연속적으로 처리하는 방식
- 가장 오래된 GC이며 Mark-and-Compact 알고리즘을 사용한다.
- GC가 수행될 때 STW가 발생한다.
- Parallel GC
- 자바 7, 8 버전에서 Default GC
- Parallel GC의 목표는 다른 CPU가 GC의 진행시간 동안 대기 상태로 남아 있는 것을 최소화 하는 것
- GC 작업을 병렬로 처리하여 STW 시간이 비교적 짧다
- Parallel Compacting GC
- Parallel GC 에서 Old Generation의 처리 알고리즘을 변경했다.
- Concurrent Mark-Sweep(CMS) GC - 가장 많이 사용함(약 1년 전 기준)
- Application의 Thread와 GC Thread가 동시에 실행되어 STW를 최소화하는 GC
- Parallel GC와는 Compaction 작업의 유무로 구분된다.
- Garbage First(G1) GC - 가장 많이 사용함(약 1년 전 기준)
- 큰 메모리에서 사용하기 적합한 GC (대규모 Heap 사이즈에서 짧은 GC 시간을 보장하는데 목적을 둔다)
- 전체 Heap 영역을 Region이라는 영역으로 분할하여 상황에 따라 역할이 동적으로 부여된다.(YG, S0, S1, OG처럼 역할이 고정되어있지 않다.)
- Z GC
- ZPage라는 영역을 사용하며, G1 GC의 Region은 크기가 고정인데 비해 ZPage는 2mb 배수로 동적으로 운영된다.
- 정지 시간이 최대 10ms를 초과하지 않은 것을 목적으로 운영된다.
- Heap 크기가 증가하더라도 정지 시간이 증가하지 않는다.