JVM, JRE, JDK란?
- JVM(Java Virtual Machine)
- 컴파일 이후, 바이트코드로 구성된 자바 파일(
.class
확장자)을 기계어로 해석하여 운영체제에서 실행시키는 역할을 합니다.
- 컴파일 이후, 바이트코드로 구성된 자바 파일(
- JRE(Java Runtime Environment)
- JVM을 포함하여 자바 프로그램을 실행시키기 위해 필요한 라이브러리 및 기타 파일이 포함된 패키지
- 자바 실행 환경이라고 이해할 수 있습니다.
- java.exe 파일이 포함되어 있습니다.(JVM을 구동시키기 위한 명령 프로그램)
- JDK(Java Development Kit)
- 자바 프로그래밍을 위해 필요한 도구
- 대표적으로 컴파일러(javac.exe)가 JDK에 포함됩니다.
JVM 구성요소
JVM은 크게 네가지 구성요소로 나눌 수 있다.
출처 : https://steady-coding.tistory.com/305
✔️ 클래스 로더(Class loader)
- JVM 내로 클래스 파일을 로딩하는 역할을 수행하는 모듈로써 런타임 내에서 동적으로 파일을 로딩합니다.
- JVM이 동작하는 중에 클래스 파일을 참조하는 순간 클래스 로더를 통해 파일을 로딩해 온다고 이해할 수 있다.
✔️ 실행 엔진(Execution Engine)
- 클래스 로더를 통해 메모리에 할당된 클래스 파일들에 담긴 바이트 코드를 컴퓨터가 이해할 수 있는 네이티브 코드로 변환하는 역할을 한다.
변환하는 과정에서 인터프리터와 JIT 컴파일러가 사용 목적에 맞게 사용된다.
- 인터프리터(Interpreter)
- 바이트 코드를 한 행씩 읽고 해석
- 중복된 코드를 구분하지 않고 해석하기 때문에 속도가 느린 단점이 있다.
- JIT 컴파일러(“Just In Time” Compiler)
- 인터프리터의 단점을 보안하기 위해 만들어진 컴파일러
- JIT가 먼저 중복 코드에 대해서 네이티브 코드로 변환시킨다.
- 코드 변환 시, 코드의 결과값을 캐싱하고 이를 활용해 중복코드를 변환한다.
- 코드 변환 과정에서 바이트코드를 바로 네이티브 코드로 변환하는 것이 아니라 IR(Intermediate Representation)코드를 거쳐 최적화한 다음 네이티브 코드로 변환한다.
✔️ 가비지 콜렉터(Garbage Collector)
- 메모리 영역 중 힙 메모리 영역에서 더 이상 사용되지 않는 메모리를 삭제하는 역할을 수행합니다.
- GC의 존재로 인해 개발자가 직접 메모리 정리에 대한 고민을 하지 않아도 되게 되었다.
✔️ 런타임 데이터 영역(Runtime Data Area)
JVM 동작 시 로딩 및 생성되는 데이터를 목적에 맞게 구분하여 저장하는 공간을 의미한다.
- Method Area
- 모든 쓰레드가 공유하는 공간
- 기본형 변수, static 변수, 클래스, 메소드, 인터페이스 등의 바이트 코드를 저장하는 공간
- Heap Area
- 모든 쓰레드가 공유하는 공간
- new 예약어로 생성된 객체 또는 배열의 바이트코드가 저장되는 공간
- 참조하는 변수나 배열이 없다면 GC의 대상이 된다.
- 메소드 영역에 로드된 클래스만 생성 가능하다.
- Stack Area
- 스택 구조로 구성된 메모리 공간으로 호출된 메소드에 대한 변수들을 저장하고 수행이 끝나면 해당 메소드의 스택 메모리는 삭제한다.
- PC register
- 쓰레드가 시작할 때 생성되며, 쓰레드가 현재 수행할 명령을 임시로 저장해두는 공간
- JVM이 현재 수행하는 명령의 주소를 가진다.
- Native Method Stack
- 바이트 코드 외 네이티브 코드를 저장하는 공간
JVM 클래스 로더
특징
- 계층 구조
- JVM 내 클래스 로더의 종류도 여러가지인에 이 클래스 로더들 사이에는 계층이 존재한다.
부모-자식관계
를 생성- 부트스트랩 클래스 로더 > 확장 클래스 로더 > 어플리케이션 클래스 로더 순으로 계층이 구성되어 있다.
- 위임 모델
- 계층 구조를 바탕으로 클래스 로더끼리 클래스 확인을 위임한다.
- 하나의 클래스 로딩과정
- 최하위 클래스 로더에서 최상위 클래스 로더까지 발견된 클래스 확인을 위임
- 최상위 클래스 로더부터 클래스명 존재 여부를 확인
- 클래스 파일이 존재하다면 해당 클래스 로더에서 클래스를 로딩
- 최하위 클래스 로더에서도 클래스가 발견되지 못하면 예외 발생
- 가시성 제한(Visiblility)
- 하위 클래스 로더에선 상위 클래스 로더의 클래스를 찾을 수 있다.
- 상위 클래스 로더에선 하위 클래스 로더의 클래스를 찾을 수 없다.
- 유일성(Uniqueness)
- 상위 클래스 로더에 로딩된 클래스는 하위 클래스 로더에서 로딩하지 못한다.
- 이를 바탕으로 클래스 로더 간의 유일성을 보장한다.
- 언로드 불가(Unload)
- 로드는 가능하지만 언로더는 불가능하다.
- 대신, 현재 클래스 로더를 삭제하고 새로운 클래스 로더를 생성하는 방법을 사용할 수 있다.
클래스 로더 위임 모델
- 부트스트랩 클래스 로더(Bootstrap Class Loader)
- JDK의 기본 API를 로딩한다.
- 기본 API는
jre/lib/rt.jar
(JRE 기본 API) 또는JAVA_HOME/lib
(JDK 기본 API)에 저장되어 있다.
- 확장 클래스 로더(Extension Class Loader)
- 자바 기본 API의 확장 클래스를 로딩한다.
- 확장 클래스는
jre/lib/ext
폴더 또는java.ext.dirs
환경변수로 지정된 폴더에 저장되어 있다.
- 애플리케이션 클래스 로더(Application Class Loader)
- 지정된
classpath
에 위치한 유저 클래스를 로딩한다. - 개발자가 만든 클래스들이 여기서 로딩된다고 보면 된다.
classpath
란 말 그대로 클래스가 저장된 경로라는 의미이며 이를 따로 지정하지 않을 경우에는 클래스파일이 위치한 현재 디렉토리가 기본classpath
로 설정된다.
- 지정된
로딩 과정
- 로딩
읽어온
.class
파일 내 바이트코드를 내용에 따라 적절한 바이너리코드로 변환한 다음, 메모리 영역 내메소드 영역
에 저장한다.모든 클래스는 각자의
.class
파일로 저장된다.- 메소드 영역에는
FQCN(Full Quallified Class Name)
, 클래스, 인터페이스, enum, 메소드, 변수 가 저장된다. - 해당 클래스 타입에 해당하는 객체는 Heap 영역에 저장한다.
- 링크
링크는 세 단계로 나뉜다.
- 검증(Verify)
- .class 파일 형식이 유효한지 확인한다.
- 준비(Preparation)
- 클래스 변수와 기본값이 필요한 메모리를 준비한다.
- Resolve
심볼릭 메모리 레퍼런스
를 메모리 영역에 있는 실제 레퍼런스와 연결한다.심볼릭 메모리 레퍼런스
는 메모리 영역 내 클래스Constant pool
에 저장되어 있다.
- 검증(Verify)
- 초기화
- 클래스 변수에 값을 할당한다.
- static 블록이 있다면 이때 실행한다.
용어 설명
FQCN : 변수, 객체, 함수 등의 계층적 구조를 모두 포함하여 표현한 것으로,
클래스가 속한 패키지명
을 모두 포함한 이름을 의미한다.예시 :
String
클래스 =>java.lang.String
로 표현하는 것이FQCN
심볼릭 메모리 레퍼런스 : 실제로 메모리 주소를 참조한 변수가 아닌 참조하는 대상의 이름만을 지칭한 변수
변수 종류에 따른 JVM 내 저장 위치
클래스 변수
는 클래스 자체가 가지는 값이기 때문에 처음 클래스 파일이 로딩되는 과정에서Method Area
에 저장됩니다. 그렇기 때문에 모든 객체가 하나의 클래스 변수 메모리 공간을 공유합니다.인스턴스 변수
는 객체가 생성될 때 메모리를 할당받고 각각의 객체마다 다른 메모리 공간을 가집니다. 객체 생성 또한new
예약어를 사용하기 때문에 인스턴스 변수 또한Heap Area
에 저장됩니다.매개 변수
와지역 변수
는기본자료형
과참조자료형
에 따라 메모리 영역이 나뉘어집니다.기본 자료형
에 경우, 해당 변수가 선언된 메소드가 호출되면서Stack Area
에 하나의 Stack 메모리가 생성되면 그 위치에 데이터 자체가 함께 저장됩니다.참조 자료형
의 경우,Stack Area
에는 참조 메모리 주소가 저장되며, 실제 데이터는 메모리 주소가 가르키는Heap Area
메모리 공간에 저장됩니다. 그러므로 참조 변수에는 실제 데이터가 아닌 데이터가 저장된 위치가 저장됩니다.
추가로 매개 변수는 하나의 메소드가 호출되면서 생성되기 때문에 이 전에 생성된 메소드에 할당된 Stack 메모리와는 다른 Stack 메모리에 저장됩니다.
예제 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Sample{
static String classStr = "Class Variable"; // 클래스 로딩과정에서 이미 메소드 영역에 메모리 할당
String instanceStr = "Instance Variable";
public static void main(String[] args){
// 클래스 변수
System.out.println(Sample.classStr);
// 인스턴스 변수
Sample sample = new Sample(); // 이 시점에 Heap 영역에서 인스턴스 변수 메모리 할당
System.out.println(sample.instanceStr);
// 지역 변수
String localStr = "Local Variable"; // main메소드에 대한 Stack 메모리가 생성된 이후, Heap 영역에 메모리 할당
// Stack Area에는 메모리 주소가 할당
System.out.println(localStr);
// 매개 변수
sample.print(localStr); // 새로운 메소드 실행과 동시에 새로운 Stack 메모리 생성, 그 안에 매개변수 값을 메모리에 할당
// 변수값의 저장 위치는 로컬변수와 동일
}
public void print(String paraStr){
paraStr = "Parameter Variable";
System.out.println(paraStr);
}
}
정리하자면
클래스 변수(Method Area
에 따로 저장)를 제외한 변수들은 Stack Area
와 Heap Area
중에 저장되며 해당 변수의 타입이 기본형인지 참조형인지에 따라 저장 위치가 결정됩니다.
변수가 저장되는 시점은 메소드가 호출됨에 따라 Stack 메모리가 할당되고, 객체가 생성되면 그 시점에 해당 객체에 해당된 인스턴스 변수가 Heap Area
에 저장됩니다.
가장 나중에 호출된 메소드가 끝나는 시점에 해당 Stack 메모리가 pop되어 제거되며 pop되는 메소드에서 선언된 지역변수나 매개 변수는 함께 제거됩니다.
참고사이트 (예제를 활용한 설명글)