JVM
자바 가상 머신으로 자바 바이트코드인 .class 파일을 해당 운영체제에 알맞는 기계어로 변환해주는 역할을 수행한다.
JVM의 두 가지 주요 기능
- 바이트코드(.class)를 해당 운영체제에 맞는 기계어로 바꾸기
- 메모리를 관리하고 최적화
자바 프로그램 실행 과정
- 컴파일
- 소스코드의 .java 파일들은 자바 컴파일러(javac.exe)를 통해 바이트코드(.class)로 변환
- 바이트코드 : JVM 내에서 실행 가능한 중간 형태의 코드
- 컴파일 단계 종료
- 로드(load)
- 클래스 로더(class loader)가 클래스들을 메모리에 로드하는 과정
- 런타임 단계 시작
- 링킹(linking)
- 검증(Verification): 올바른 바이트코드인지 검증
- 준비(Preperation): 동적로딩(dynamic loading)을 통해 Runtime Data Area, 즉 JVM 내의 메모리에 로드
- 해결(Resolution): 외부에 있는 다른 클래스나 메소드와의 연결을 수행
- 초기화(Initialization): 클래스가 최초로 사용될 때, 정적(static) 변수들의 할당 및 초기화를 진행
- 실행
- 바이트코드를 JVM 내의 인터프리터 또는 JIT 컴파일러를 통해 기계어로 번역하고 실행
- JIT(Just-In-Time) 컴파일러 : 인터프리터로 실행하다가 적절한 시점에 바이트코드 전체를 번역하여 기계어(네이티브 코드)로 변환하여 캐시에 저장한다.
JVM 구조

클래스 로더(Class Loader)가 자바 바이트 코드를 런타임 데이터 영역(Runtime Data Area)에 로드하고, 실행 엔진(Execution Engine)이 바이트 코드를 실행한다.
1. 클래스 로더(Class Loader)
동적 로드
컴파일 단계가 아닌 런타임 단계에서 필요한 클래스들을 그 때마다 로딩
- 장점 : 컴파일 단계에 모든 클래스들을 다 로딩할 필요가 없고, 메모리도 아낄 수 있다.
- 단점 : 런타임 에러를 알 수 없다.
2. 런타임 데이터 영역(Runtime Date Areas)

-
JVM이 운영체제 위에서 실행되면서 할당 받은 메모리
-
PC 레지스터(PC Register), JVM 스택(JVM Stack), 네이티브 메서드 스택(Native Method Stack)은 스레드마다 하나씩 생성
-
힙, 메서드 영역, 런타임 상수 풀은 모든 스레드가 공유하고 JVM 당 하나씩 생성된다.
런타임 데이터 영역의 구조
1. 힙(Heap)
- 실제 데이터를 가진 인스턴스, 배열 등을 저장
- 힙 영역이 가득 차게 되면 OutOfMemoryError가 발생
- 가비지 컬렉션의 대상
- JVM 성능 이슈의 핵심
2. 메서드 영역(Method Area)
- JVM이 읽어 들인 각각의 클래스와 인터페이스에 대한 런타임 상수 풀, 필드와 메서드 정보, Static 변수, 메서드의 바이트코드 등을 보관
- 가비지 컬렉션의 대상
- 런타임 상수 풀(Runtime Constant Pool)
- 어떤 메서드나 필드를 참조할 때 JVM은 런타임 상수 풀을 통해 해당 메서드나 필드의 실제 메모리상 주소를 찾아서 참조
4. JVM 스택
- 각 스택 프레임(Frame)은 지역 변수 배열(Local Variable Array), 피연산자 스택(Operand Stack), 현재 실행 중인 메서드가 속한 클래스의 런타임 상수 풀에 대한 레퍼런스를 갖는다.
- 지역 변수 배열, 피연산자 스택의 크기는 컴파일 시에 결정되기 때문에 스택 프레임(frame)의 크기도 메서드에 따라 크기가 고정된다.
- 만약 프레임에서 수행하는 계산이 허용된 JVM 스택 크기를 초과한다면, JVM은 StackOverflowError를 발생시킨다.
- 지역 변수 배열(Local Variable Array)
- 메서드의 지역 변수들을 저장
- 0부터 시작하는 인덱스를 가진 배열
- 0은 메서드가 속한 클래스 인스턴스의 this 레퍼런스이고, 1부터는 메서드에 전달된 파라미터들이 담김
- 피연산자 스택(Operand Stack)
- 메소드 내 연산을 위해서, 바이트 코드 명령문들이 들어있는 공간
- 메서드의 실제 작업 공간
5. PC 레지스터(PC registers)
- 현재 실행되고 있는 명령어의 주소를 저장
- 멀티쓰레드 환경에서 한 쓰레드가 다른 쓰레드로 잠시 CPU 점유를 넘겨주고 돌아왔을 때, 이전에 어떤 명령을 수행하고 있었는지 기억하고 있어야 이전 작업을 수행할 수 있다. 이를 위해서 기억하는 역할을 한다.
6. 네이티브 메서드 스택(Native Method Stack)
- 자바 외의 언어로 작성된 네이티브 코드를 위한 스택(주로 C나 C++)
JVM 스택, PC 레지스터, 네이티브 메서드 스택은 thread가 생성될 때마다 같이 생성되고, 서로 다른 thread가 침범할 수 없다. 그렇기 때문에 하나의 메서드 안에서 지역 변수의 동시성 문제(Concurrency Problem)을 걱정하지 않아도 된다.
3. 실행 엔진(Execution Engine)
-
클래스 로더를 통해 JVM 내의 런타임 데이터 영역에 배치된 바이트코드는 실행 엔진에 의해 실행
-
바이트 코드를 기계어로 바꾸는 과정을 거치는데 인터프리터 방식과 JIT 컴파일러 방식이 있다.
-
인터프리터 : 코드를 한줄 한줄 번역해서 실행하는 것
-
JIT 컴파일러 : 인터프리터 방식으로 실행하다가 적절한 시점에 바이트코드 전체를 컴파일하여 네이티브 코드로 변경하고, 이후에는 해당 메서드를 더 이상 인터프리팅하지 않고 네이티브 코드로 직접 실행하는 방식으로 캐싱 기능까지 있어서 나중에는 캐싱된 결과를 실행하기도 한다.
-
가비지 컬렉션(Garbage Collection)
- JVM의 효율적인 메모리 관리를 위해 가비지 컬렉터(Garbage Collector)가 메모리 관리를 자동으로 해준다.
- 어떤 참조 변수에 의해서도 참조되지 않는 객체는 의미가 없기 때문에 소멸되어야 한다.
- 모든 객체에는 count 변수가 있어서 참조 변수가 생성될 때마다 count 변수의 값이 증가하고 참조가 되지 않을 때마다 count 값이 감소된다. count 값이 0이 되면 가비지 컬렉션에 의해 없어진다.
- count 값이 0이 되었다고 바로 없애지는 않는다. 왜냐하면 너무 잦은 가비지 컬렉션은 오히려 시스템에 부담을 줄 수 있기 때문에 임의의 시간이 지난 후 가비지 컬렉션이 동작한다.
- 단점
- 메모리가 언제 어떻게 해제되었는지 알 수 없다.
- 가비지 컬렉션(GC)이 동작하는 동안에는 다른 동작을 멈추기 때문에 오버헤드가 발생한다.
참고 : https://parkadd.tistory.com/20