1. JVM 이란?
자바 가상 머신 (Java Virtual Machine, JVM)은 시스템 메모리를 관리하면서 자바 기반 애플리케이션을 위해 이식 가능한 실행 환경을 제공한다. 한마디로 JVM은 자바 애플리케이션을 실행하는 프로그램이다. 이를 통해 자바는 OS 에 종속되지 않고, JVM이 존재한다면 실행 가능하다. Stack 기반의 가상 머신이다.
2. JVM의 용도와 정의
JVM에는 2가지 기본 기능이 있다.
- 자바 프로그램을 어떤 환경 (기기, 운영체제 등등)에서도 실행될 수 있도록 하는것
- 프로그램 메모리를 관리하고 최적화 하는 것 (메모리 관리, Garbage collection)
JVM은 기술적인 정의와 소프트웨어 개발자들의 일반적인 정의를 통해 두가지 정의를 나눌 수 있다.
- 기술적 정의: JVM은 코드를 실행하고 해당 코드에 대한 런타임 환경을 제공하는 소프트웨어 프로그램에 대한 사양 (specification) 이다.
- 일반적 정의: 자바 프로그램을 실행하는 방법이다. JVM의 설정을 구성한 다음 설정사항에 따라 실행 중에 프로그램 리소스를 관리한다.
※ JVM, JRE, JDK 구분
- JVM
자바 바이트 코드 들을 OS에서 어떻게 실행할 지에 대한 표준 사양으로 다양한 구현체가 존재한다. 자바 언어로 개발하기 때문에 OS에 독립적이란 의미지 JVM 자체는 OS마다 어떻게 실행되어야 하는지 다르게 구현되어 있기 때문에 JVM은 OS에 종속적이라고 할 수 있다.
- JRE (Java Runtime Environment)
자바 애플리케이션을 실행하기 위한 최소 배포단위로 JVM과 library가 포함되어있다. 실행 목적으로 개발 관련 도구는 포함되지 않는다.
오라클은 자바 11부터 JDK만 제공하며 JRE를 따로 제공하지 않는다.
- JDK (Java Development Kit)
JRE + 개발에 필요한 툴이 포함된다. 소스코드를 작성할 때 사용하는 자바는 플랫폼 독립적이다.
3. JVM의 기본 구성
JVM은 크게 Garbage collector, Execution Engine, Class Loader, Runtime Data Area 4가지 영역으로 나눠집니다.
1) Garbage Collector (GC)
자바에서는 JVM이 프로그램 메모리를 관리한다. JVM은 가비지 컬렉션이란 프로세스를 통해 메모리를 관리하며, 가비지 컬렉터는 자바 프로그램에서 사용되지 않는 메모리를 지속적으로 찾아내서 제거한다. 가비지 컬렉션은 실행 중인 JVM 내부에서 일어난다.
자동으로 실행되기 때문에 언제 정확히 실행되는지 알기 어렵다. 사용자가 임의로 GC를 발생시키는 것은 좋은 방법이 아니기 때문에 garbage collector에게 맡기는 것이 안전하다. 기본적으로 GC가 수행되는 동안 GC를 실행하는 스레드 외에 모든 스레드가 일시정지 된다.
2) Class Loader
개발의 결과물로 생성된 자바 소스 파일 .java 파일이 컴파일러 (javac)에 의해 .class 파일 즉 자바 바이트 코드로 컴파일 되고, 이렇게 컴파일 된 바이트 코드들을 class loader가 운영체제에 의해 메모리를 할당 받은 runtime data area로 적재하는 역할을 한다.
JVM 내부로 클래스를 로드하고, 링크를 통해 배치하는 작업을 수행하는 모듈이다. runtime 시에 동적으로 클래스를 로드한다. .jar 파일 내 저장된 클래스 들을 JVM 위에 탑재하고 사용하지 않는 클래스들은 메모리에서 삭제한다.
자바는 동적코드 이기 때문에 컴파일 타임이 아니라 런타임에 참조한다. 즉 클래스를 처음 참조할 때 해당 클래스를 로드하고 링크한다.
3) Execution Engine
class loader 에 의해 runtime data area에 적재된 .class 파일들을 하나의 명령단위로 읽어서 컴퓨터가 이해할 수 있는 기계어로 번역하고 명령을 실행한다.
자바 바이트 코드를 기계어로 변환하는 방식으로는 interpreter 방식과 JIT (just in time) 방식이 존재한다.
Interpreter
자바 바이트 코드를 명령어 단위로 읽어서 실핸한다. 한 줄씩 읽어서 수행하기 때문에 속도가 느리다.
파이썬과 같은 스크립트 언어들일 인터프리터를 사용하여 실행된다.
JIT
인터프리터 방식의 단점을 보완하기 위해 도입되었다. 인터프리터 방식으로 실행하다가 적절한 시점에 바이트 코드 전체를 컴파일 하여 네이티브 코드로 변경하고, 이후에는 더이상 인터프리팅하지 않고 네이티브 코드로 직접 실행하는 방식이다.
네이티브 코드는 캐시에 보관하기 때문에 한번 컴파일 된 코드는 빠르게 수행하게 된다.
4) Runtime Data Area
JVM 메모리 영역으로 OS 로부터 별도로 메모리 공간을 할당받고 자바 애플리케이션을 실행할 때 사용된다.
method area, heap area, stack area, PC register, native method stack 총 5가지로 구분된다.
Method Area (Static area, Class area)
클래스 정보를 처음 메모리 공간에 올릴 때 초기화 되는 대상을 저장하기 위해 사용하는 메모리 공간. 자바 애플리케이션이 실행되면 클래스 별로 필요한 패키지 클래스, 런타임 상수풀, 인터페이스, 상수, static 변수, final 변수, 클래스 멤버 변수 등 필드 데이터, 생성자를 포함한 모든 메서드 정보 등이 한번 로드 된 후 메모리에 항상 상주하고 있는 영역이다. 그래서 모든 스레드가 공유 가능하다.
Heap Area
method area가 클래스 데이터를 위한 공간이라면 heap area는 객체를 위한 공간이다. new를 통해 동적으로 생성된 객체, 배열, immutal 객체 등의 값이 저장된다. 해당 영역에서 생성된 객체들은 Stack Area의 변수나 다른 객체의 필드에서 참조가 가능하다. 이 말은 모든 스레드에서 공유한다는 의미로, 동기화 문제가 발생할 수 있다.
해당 영역에 할당되는 객체들은 타 영역에서 참조하는데, 만약 객체가 더이상 참조되지 않는 경우 GC에 의해 heap 영역에서 제거된다.
Heap 영역은 다음과 같이 구성되어 있다.
- New/Young Generation
Eden: 객체들이 최초로 생성되는 공간
Survivor 0/1: Eden에서 참조되는 객체들이 저장되는 공간
Eden 영역에 객체가 가득 차게 되면 첫번째 GC가 발생하여 Eden 영역에 있는 값들은 Survivior1 영역에 복사하고 이 영역을 제외한 나머지 영역의 객체를 삭제한다.
- Tenured Generation (Old area)
New area 에서 일정시간 참조되어 살아남은 객체들이 저장되는 공간이다.
- Permanent Generation
생성된 객체들의 정보와 주소 값이 저장되는 공간이다. class loader에 의해 적재되는 class, method 등에 대한 meta 정보가 저장되는 영역이고, JVM에 의해 사용되어진다.
reflection을 사용하여 클래스가 로딩되는 경우에 사용된다. 내부적으로 reflection 기능을 자주 사용하는 spring framework 를 이용하는 경우 이 영역에 대한 고려가 필요하다.
Stack Area
스택 영역은 각 스레드마다 하나씩 존재하면 스레드가 시작될 때 할당된다.
프로그램 실행 과정에서 임시로 할당되었다가 메서드를 빠져나가면 바로 소멸되는 특성의 데이터를 저장하기 위한 영역으로 메서드에서 직접 사용하는 지역 변수, 파라미터, 리턴 값, 참조 변수의 주소값, 연산을 위한 임시 값 등이 저장된다.
메서드 호출 시마다 스택 프레임(해당 메서드 만을 위한 공간)이 생성되고 (stack에 push 된다) 메서드 수행이 끝나면 프레임 별로 삭제된다. (stack 에서 pop 된다)
PC Register
PC register는 Thread가 생성될 때마다 생성되는 영역으로 program counter, 현재 스레드가 실행되는 부분의 주소와 해당 명령을 저장하는 영역이다. 이를 통해서 다수의 스레드들이 명령의 흐름을 잃지 않고 함수를 순차적으로 실행할 수 있다.
Native Method Stack
자바가 아닌 다른 언어로 작성 된 네이티브 코드, 기계어 프로그램을 수행하기 위한 메모리 영역이다. Java Native Interface를 통해 바이트 코드로 전환하여 저장하게 된다.
Java 프로그램 실행 과정
1) 프로그램이 실행되면 JVM은 OS로부터 프로그램이 필요로 하는 메모리를 할당받는다. JVM은 이 메모리를 용도에 따라 여러 영역으로 나누어 관리한다.
2) 자바 컴파일러 (javac)가 자바 소스코드 (.java 파일)을 읽어서 자바 바이트 코드 (.class)로 컴파일, 변환한다.
3) Class Loader가 .class 파일들을 읽어서 JVM으로 로딩한다.
Loading: Bootstrap class loader, Extension class loader, Applicatuion class loader 3개의 클래스 로더를 통해 .class 파일이 JVM으로 로드된다. class loading을 요청받았을 때 bootstrap -> extension -> application 순으로 우선권을 가지고 실행되며 앞순서의 class loader에 해당 class가 없다면 다음 class loader로 넘어가는 식으로 실행된다.
- Bootstrap: 최상위 class loader로 java.lang.Obejct 같은 핵심 시스템 class를 로딩한다.
- Extension: 사용자의 classpath를 수정하지 않고 다양한 보안 확장 프로그램 같은 새로운 확장 프로그램을 로드할 수 있다.
- Application: $CLASSPATH에 위치한 JAR 파일을 로딩한다. 직접 만든 애플리케이션의 jar 파일의 classpath 라고 보면된다.
Linking: verification과 preparation, resolution 단계를 거치면서 바이트 코드를 검증하고 필요한 만큼의 메모리를 할당한다.
- Verify: 로드된 .class 파일들의 에러를 확인한다.
- Prepare: .class 파일들을 훓으면서 static 키워드가 붙은 멤버들에 대해 메모리만 할당하고 기본값으로 세팅한다.
- Resolve: .class 파일들을 훓으면서 symbolic reference 들을 method area 에서 실제 주소값을 찾고 그 주소값으로 참조 객체들을 세팅한다.
Initialization: 모든 static block의 값이 초기화 되고 static 데이터 들의 원래 값으로 할당된다.
4) 로딩된 .class 파일들은 Execution Engine 을 통해 해석된다. (기계어로 번역된다.)
5) 해석된 바이트 코드들은 Runtime Data Area 에 배치되어 실질적인 수행이 이루어지게 된다.
[reference]
- https://honbabzone.com/java/java-jvm/
- https://dzone.com/articles/jvm-architecture-explained
- https://jithub.tistory.com/40
'프로그래밍언어 > JAVA' 카테고리의 다른 글
[JAVA] 진법 및 진법변환 (0) | 2021.08.01 |
---|---|
[JAVA] 변수의 타입 (0) | 2021.07.29 |
[JAVA] 변수와 상수 (0) | 2021.07.28 |
[JAVA] 자바 개발환경 구축 및 프로그래밍 시작 (0) | 2021.07.27 |
[JAVA] 자바 (Java Programming Language) (0) | 2021.07.27 |