M8z.log
JVM(Java Virtual Machine) 본문
1. 개요
JVM(Java Virtual Machine)은 자바 코드(.java)를 컴파일해서 얻은 바이트 코드(.class)를 메모리에 배치하고 배치된 바이트 코드를 해당 운영체제가 이해할 수 있는 기계어로 바꾸어 실행시켜주는 역할을 한다.
‘Write once, run anywhere’ 라는 말 처럼 JVM만 설치되어 있으면 동일한 자바 바이트 코드를 어떠한 플랫폼에서도 실행시킬 수 있다.
1.1. 주요 특징
- 스택 기반의 가상 머신
- 가비지 컬렉션(Garbage Collector) - 힙 메모리 영역에 생성된 객체들 중에서 참조되지 않은 객체들을 탬색 후 제거하는 역할
- 데이터 흐름 분석(data flow analysis)에 기반한 자바 바이트코드 검증기를 통해 스택 오브플로우, 명령어 피연산자의 타입 규칙 위반, 필드 접근 규칙 위반, 지역 변수의 초기화 전 사용 등 많은 문제를 실행 전에 검증하여 실행 시 안전을 보장
1.2. JRE와 JDK
자바 언어로 컴파일된 프로그램을 실행하거나 개발하기 위해서는 그에 해당하는 프로그램 설치가 필요하다.
Oracle JDK download : https://www.oracle.com/java/technologies/downloads/
OpenJDK download : https://jdk.java.net/archive/
OpenJDK는 버전 7 이후로 자바 SE의 공식 레퍼런스 구현체이다.
1.2.1. JRE
자바 실행 환경(Java Runtime Eniroment)으로 자바로 만들어진 프로그램을 실행시키는데 필요한 라이브러리들과 API, 그리고 자바 가상 머신(JVM)이 포함되어 있다.
1.2.2. JDK
자바 개발 키트(Java Development Kit)로 개발에 필요한 라이브러리들과 컴파일러와 개발 도구들이 들어 있으며 JRE도 함께 포함되어 있다.
JDK에 포함되어 있는 개발 소프트웨어
- javac : 자바 컴파일러로 자바 소스를 바이트 코드로 변환
- java : 자바 프로그램 실행기, JVM을 작동시켜 자바 프로그램 실행
- javadoc : 자바 소스로 부터 HTML 형식의 API 도큐먼트 생성
- javap : 클래스 파일의 바이트 코드를 소스와 함께 보여주는 디어셈블러
- jdb : 자바 응용프로그램의 실행 중 오류를 찾는 데 사용하는 디버거
- jlink : 응용프로그램에 맞춘 맞춤형 JRE 생성
- jmod : 자바의 모듈 파일(.jmd)을 만들거나 모듈 파일의 내용 출력
- jar : 자바 클래스 파일을 압축한 자바 아카이브 파일(.jar) 생성, 관리
1.3. JVM 실행 및 메모리 영역
JVM의 구조는 크게 Class Loader, Execution Engine, Runtime Data Area, Garbage Collector로 나눈다.

[이미지 출처] https://limkydev.tistory.com/51
1.3.1. 실행 과정
자바 컴파일러로 자바 소스(.java)를 바이트 코드(.class)로 컴파일한다.
프로그램을 실행하면 Class Loader가 컴파일된 바이트코드를 묶어서 운영체제로 부터 할당 받은 메모리 영역(Runtime Data Area의 Method Area)에 배치한다. 자바는 동적으로 클래스를 읽어오므로 프로그램 실행 중인 런타임에서야 모든 코드가 JVM과 연결된다.
배치된 이후에 JVM은 Method Area의 바이트 코드를 Exceution Engine에 제공하여 정의된 내용대로 바이트 코드를 실행시킨다. 실행 시키는 두가지 방법, Interpreter방식과 JIT(Just-In-Time)방식이 있다.
Interpreter 방식
바이트 코드를 명령어 단위로 읽어서 실행한다. 느리다는 단점이 있다.
JIT(Just-In-Time) 방식
인터프리터 방식을 보완하기 위해 도입, 인터프리터 방식으로 실행하다가 적절한 시점에 바이트 코드 전체를 컴파일하여 네이티브 코드로 변경하고 이후에는 더 이상 인터프리팅 하지 않고 네이티브 코드를 직접 실행하는 방식이다.
1.3.2. 메모리 영역 - 런타임 데이터 영역(Runtime Data Area)
자바 애플리케이션을 실행할 때 사용되는 데이터들을 적재하는 영역이다. Method Area, Heap Area, Stack Area, PC Register, Native Method Stack으로 나눌 수 있다.

[이미지 출처] https://tecoble.techcourse.co.kr/post/2021-08-09-jvm-memory/
모든 스레드가 공유해서 사용(GC의 대상) - Heap Area, Method Area
스레드마다 하나씩 생성 - Stack Area, PC Register, Native Method Stack
1.3.2.1. 메서드 영역 (Method Area)
인스턴스 생성을 위한 객체 구조, 생성자, 필드 등이 저장된다. 클래스 멥버 변수의 이름, 데이터 타입, 접근 제어자 정보와 같은 각종 필드 정보들과 메서드 정보, 클래스의 Type정보, Constant Pool(상수 풀: 문자 상수, 타입, 필드, 객체 참조가 저장됨), static 변수 등이 생성되는 영역이다. 인스턴스 생성에 필요한 정보도 존재하기 때문에 JVM의 모든 스레드들이 Method Area를 공유하게 된다. 기초 역할을 하므로 JVM 구동 시작 시에 생성이 되며 종료 시까지 유지되는 공통 영역이다.
1.3.2.2. 힙 영역 (Heap Area)
코드 실행을 위한 Java로 구성된 객체 및 JRE 클래스들이 탑재된다. 이곳에서는 문자열에 대한 정보를 가진 String Pool *뿐만이 아니라 실제 데이터를 가진 인스턴스, 배열 등이 저장이 된다. JVM 당 역시 하나만 생성이 되고, 해당 영역이 가진 데이터는 모든 Java Stack *영역에서 참조되어, 스레드 *간 공유가 된다. Heap *영역이 가득 차게 되면 OutOfMemoryError *를 발생시키게 된다. 다음은 인스턴스의 영역을 가득 차게 만들어서 해당 Heap *영역에서의 Error 발생시키는 코드이다.
public class Heap {
public static void main(String[] args) {
System.out.println("Heap 메모리 오류");
int num = 1;
List<Integer> nums = new LinkedList<>();
try {
while (true) {
nums.add(num);
num = num + 1;
if (num < 1) {
break;
}
}
} catch (Exception e) {
System.out.println(e);
}
}
}
[출처] https://tecoble.techcourse.co.kr/post/2021-08-09-jvm-memory/

[이미지 출처] https://coding-factory.tistory.com/828
Heap Area는 효율적인 GC를 위해 위와 같이 크게 3가지의 영역으로 나뉘게 된다.
- Young GenerationMinor GC : New 영역에서 일어나는 과정
- 최초에 객체가 생성되면 Eden영역에 생성된다.
- Eden영역에 객체가 가득차게 되면 첫 번째 GC가 일어난다.
- survivor1 영역에 Eden영역의 메모리를 그대로 복사되고 survivor1 영역을 제외한 다른 영역의 객체를 제거한다.
- Eden영역도 가득차고 survivor1영역도 가득차게되면 Eden영역에 생성된 객체와 survivor1영역에 생성된 객체 중에 참조되고 있는 객체가 있는지 검사한다.
- 참조 되고있지 않은 객체는 내버려두고 참조되고 있는 객체만 survivor2영역에 복사한다.
- survivor2영역을 제외한 다른 영역의 객체들을 제거한다.
- 위의 과정중에 일정 횟수이상 참조되고 있는 객체들을 survivor2에서 Old영역으로 이동시킨다.
- 위 과정을 계속 반복, survivor2영역까지 꽉차기 전에 계속해서 Old로 비움
- Heap영역에 객체가 생성되면 최초로 Eden 영역에 할당된다. 이 영역에 데이터가 어느정도 쌓이게 되면 참조 정도에 따라 Servivor의 빈 공간으로 이동되거나 참조 정보가 없으면 회수(Minor GC)된다. 살아남은 객체들 중 더 오래 쓸 것들은 tenured generation으로 옮긴다.
- Tenured Generation
- Old영역에 할당된 메모리가 허용치를 넘게 되면, Old 영역에 있는 모든 객체들을 검사하여 참조가 없는 객체들을 표시하고 그 해당 객체들을 모두 제거하는 GC가 실행된다. 이 때 GC를 실행하는 스레드를 제외한 모든 스레드는 작업을 멈추게 된다. 이유는 Heap 메모리 영역에 중간중간 빈 메모리 공간이 생기는데 이 부분을 없애기 위해 재구성을 하게 된다. 따라서 메모리를 옮기고 있는데 다른 스레드의 메모리를 사용을 막기 위해 모든 쓰레드를 정지하게 되는 것이다. 이를 'Stop-the-World' 라 한다. 그리고 이렇게 'Stop-the-World'가 발생하고 Old영역의 메모리를 회수하는 GC를 Major GC라고 한다.
- Permanent Generation
- JDK7까지는 Permanent영역이 Heap에 존재했다. JDK8 부터는 ‘Meta Space 영역’으로 변경되었다. Meta Space 영역은 Native Stack 영역에 포함되었다.
1.3.2.3. 스택 영역 (Stack Area)
각 스레드 별로 따로 할당되는 영역이다. Heap 메모리 영역보다 비교적 빠르다는 장점이 있다. 스레드 별로 메모리를 따로 할당하기 때문에 동시성 문제에서 자유롭다는 점도 있다.
스레드들은 메서드를 호출할 때마다 프레임이라는 단위를 추가(push)하게 된다. 메서드가 마무리되고 결과를 반환하면 해당 프레임은 스택으로 부터 제거(pop)된다.
지역변수, 파라미터, 리턴 값, 연산에 사용되는 임시 값 등이 생성되는 영역이다.
스택에 프레임이 쌓이는 구조 및 힙 영역과의 참조를 보여주는 예제
class Person {
int id;
String name;
public Person(int id, String name) {
this.id = id;
this.name = name;
}
}
public class PersonBuilder {
private static Person buildPerson(int id, String name) {
return new Person(id, name);
}
public static void main(String[] args) {
int id = 23;
String name = "John";
Person person = null;
person = buildPerson(id, name);
}
}
[출처] https://www.baeldung.com/java-stack-heap

[이미지 출처] https://www.baeldung.com/java-stack-heap
new 키워드로 생성한 객체와 String 변수 값은 String Pool로 Heap 영역에 저장되고 지역 변수와 String Pool의 값을 참조하는 String reference는 스택 영역에서 참조한다.
스택 영역이 가득 차게 되면 StackOverflowError를 발생시킨다.
StackOverflowError 발생 예제
public class Stack {
public static void main(String[] args) {
System.out.println("Stack 메모리 오류");
try{
int num = func(0);
} catch (Error e) {
System.out.println(e);
}
}
private static int func(int num) {
num = num+1;
return func(num);
}
}
[출처] https://tecoble.techcourse.co.kr/post/2021-08-09-jvm-memory/
1.3.2.4. PC(Program Counter)레지스터 (PC Register)
스레드가 생성될 때마다 생성되는 영역으로 프로그램 카운터, 즉 현재 스레드가 실행되는 부분의 주소와 명령을 저장하고 있는 영역이다.
자바에서 스레드는 각자의 메서드를 실행하게 된다. 이때 스레드 별로 동시에 실행하는 환경이 보장되어야 하므로 최근에 실행 중인 JVM에서는 명령어 주소값을 저장할 공간이 필요하다. 이 부분을 PC Register 영역이 관리하여 추적해 준다.
1.3.2.5. 네이티브 메서드 스택 (Native Method Stack)
자바 이외의 언어(C, C++, 어셈블리 등)로 작성된 네이티브 코드를 실행할 때 사용되는 메모리 영역으로 C Stack이라 불리기도 한다. 자바 스택 영역과 비슷하게 Native Method가 실행될 경우 Stack에 해당 메서드가 쌓이게 된다. 스레드들이 생성되면 Native Method Stack도 동일하게 생성된다.
보통 C/C++ 등의 코드를 수행하기 위해 JNI 자바 컴파일러에 의해 변환된 자바 바이트 코드를 읽고 해석하는 역할을 하는 것이 자바 인터프리터(interpreter)이다.
1.3.3. Gabage Collector
Serial Collector
- 싱글 스레드로 모든 종류의 가비지 컬렉션을 수행
- 싱글 프로세서 시스템에 가장 적합
- 시스템 환경에 따라 선택되거나, -XX:+UseSerialGC 옵션으로 선택할 수 있다.
Parallel Collector
- 마이너 컬렉션을 병렬로 수행한다. GC의 오버헤드를 현저하게 줄일 수 있다.
- 멀티 프로세스나 멀티 스레드 하드웨어에서 돌아가는 중대형 규모의 데이터셋을 다루는 애플리케이션을 위한 GC.
- 시스템 환경에 따라 선택되거나, -XX:+UseParallelGC 옵션으로 선택할 수 있다.
Concurrent Collectors
- 전체 처리율보다 응답 시간이 더 중요한 경우에 사용할 것.프로세서의 수를 늘릴수록 효과를 볼 수 있지만 한계가 있음.
- Concurrent Mark Sweep(CMS) Collector, Garbage-First Garbage Collector
- 프로세서가 GC와 처리 역할을 나누어 일하기 때문에 일시 정지가 짧아진다.
Ref
https://ko.wikipedia.org/wiki/자바_가상_머신
https://limkydev.tistory.com/51
https://memostack.tistory.com/226
https://coding-factory.tistory.com/826
https://coding-factory.tistory.com/828
https://asfirstalways.tistory.com/158
https://jeong-pro.tistory.com/148
https://johngrib.github.io/wiki/jvm-memory/#jvm의-메모리는-generation-구조를-갖는다
https://tecoble.techcourse.co.kr/post/2021-08-09-jvm-memory/