JVM (Java Virtual Machine)
- OS를 대신해 자바 프로그램을 실행하는 가상의 OS 역할
- 운영체제는 자바 프로그램을 바로 실행할 수 없고, 자바 프로그램은 완전한 기계어가 아닌 중간 단계의 바이트 코드이기 때문에 이를 해석하고 실행할 수 있는 가상의 운영체제가 필요하다.
- 이를 가능하게 해주는 것이 JVM -> JVM은 JAVA와 OS 사이에서 자바 프로그램을 실행하는 중계자 역할을 한다.
JVM은 왜 있는가? => OS에 독립적은 JAVA 프로그램을 위하여
- 운영체제별로 프로그램을 실행하고 관리하는 방법이 달라 운영체제별로 자바를 따로 개발하는 것 보단, 운영체제와 자바 프로그램을 중계하는 JVM을 둔다.
- 그 결과, JVM을 통해 하나의 자바프로그램을 여러 운영체제에서 실행하더라도 동일한 결과가 나온다.
특징
- OS와 상관없음
- 바이트 코드는 모든 JVM에서 동일한 실행 결과를 보장
- JVM은 운영체제에 종속적
- JVM은 운영체제에 맞게 설치되어야 함
- 바이트 코드는 하나지만, JVM에 의해 번역되는 기계어는 운영체제에 따라 달라짐
- JVM은 JDK 또는 JRE를 설치하면 자동으로 설치됨
- 스택 기반
컴파일 하는 방법
.java(java source code) => .class(byte code) => .exe
1. 개발자는 .java 파일을 작성(소스 코드)
2. 소스 코드를 컴파일러로 (javac.exe) 컴파일 하면 확장자가 .class인 바이트 코드 파일이 생성(1차)
3. 바이트 코드 파일은 JVM 구동 명령어 (javac.exe)에 의해 JVM에서 해석되고 운영체제에 맞게 기계어로 번역(2차)
ex) Hello.java 실행 순서
자바 소스 파일을 컴파일러(javac.exe)로 컴파일 -> javac Hello.java(1차 컴파일) -> 컴파일 성공 시 확장명이 .class인 바이트 코드 파일이 생성됨 -> 컴파일 결과 생성된 .class 확장자 파일은 완전한 기계어가 아니므로 단독으로 실행할 수 없고, JVM이 실행되어야 함(JVM을 구동시키는 명령어 java.exe) -> java Hello(2차 컴파일) -> java.exe 명령어 실행 시 JVM은 바이트 코드 파일을 메모리로 로드하고, 최적의 기계어로 번역 -> 이후, main 메소드를 찾아 실행
바이트코드란?
- JVM이 이해할 수 있는 언어로 변환된 자바 소스 코드
- 기계가 바로 실행할 수 있는 언어보다는 비교적 인간이 보기 편한 형태로 기술된 형태
- 실행 엔진이 한 번 더 JVM 내부에서 인식할 수 있는 형태로 변환함
JVM 구성 요소
Class Loader (클래스 로더)
- JVM 내로 클래스 (.class) 파일을 로드하고, 링크를 통해 배치하는 작업 수행
- Runtime 시에 동적으로 Runtime Data Area 에 로드
Runtime Data Area
Method Area (메소드 영역) : JVM이 시작할 때 생성되고 모든 스레드가 공유하는 영역
코드에서 사용 되는 클래스 (.class) 를 클래스 로더로 읽어서, 클래스별로
- 런타임 상수풀 (runtime constant pool)
- 필드(field) 데이터
- 메소드 (method) 데이터
- 메소드 코드
- 생성자 (constructor) 코드
등을 분류해서 저장한다.
Heap Area (힙 영역) : 객체와 배열이 생성되는 영역
- 힙 영역에 생성된 객체와 배열은 JVM 스택 영역의 변수나 다른 객체의 필드에서참조
- 만약, 참조하는 변수나 필드가 없으면, 의미 없는 객체가 되어 쓰레기로 취급 —> JVM은 Garbage Collector를 실행시켜, 쓰레기 객체를 Heap area 내에서 자동으로 제거
- GC 덕분에, 개발자는 별도 객체 제거 위한 별도 코드 작성 필요 X —> (자바는 코드로 객체를 직접 제거시키는 방법을 제공하지 않음)
JVM Stack Area (JVM 스택 영역) : 각 스레드마다 하나씩 존재하며, 스레드가 시작될 때 할당됨
- 자바 프로그램 내에서 추가적으로 스레드 생성하지 않았으면
- main 스레드만 존재 → JVM Stack도 한 개
- JVM 스택은 메소드 호출할 때 마다,
- **프레임 (Frame)**을 추가 (push)
- 메소드 종료 시, 해당 프레임 제거 (pop)
- 예외 발생 시, printStackTrace() 메소드로 보여주는 Stack Trace의 각 라인 → 하나의 프레임을 표현함
- 프레임 내부
- 로컬 변수 스택
- 기본 타입 변수나 참조 타입 변수가 추가 되거나 제거됨
- 언제 추가 및 제거?
- 변수 초기화 시에 추가 (최초로 변수에 값이 저장될 때)
- 선언된 블록을 벗어나면 스택에서 제거
- 기본 타입 변수
- 스택 영역에 직접 값을 가짐
- 참조 타입 변수
- 힙 영역이나 메소드 영역의 객체 주소를 가짐
- 로컬 변수 스택
Execution Engine (실행 엔진)
- 클래스를 실행시키는 역할을 함
- 클래스 로더가 JVM 내의 런타임 데이터 영역에 바이트 코드를 배치
- 자바 바이트 코드를 실행함
Interpreter
- 자바 바이트코드를 명령어 단위로 읽어서 실행함
- 한 줄 한 줄 실행됨
JIT Compiler
- 인터프리터 방식의 단점을 보완하기 위해 도입된 방식
- 인터프리터 방식으로 실행하다가, 적절한 시점에 바이트 코드 전체를 컴파일하여 네이티브코드로 변경하고 그 이후에는 네이티브 코드를 직접 실행함
- 네이티브 코드를 캐시에 보관하게되어, 인터프리터 방식보다 빠르게 실행됨
- 한 번만 실행되는 코드라면 인터프리터 방식으로 실행하는 것이 더 유리함
- JIT 컴파일러는 JVM 내부적으로 해당 메소드가 얼마나 자주 수행되는지 체크하고, 일정 정도가 넘을 때에만 컴파일을 수행함
Garbage Collector
- GC를 수행하는 모듈 (스레드)