![](https://blog.kakaocdn.net/dn/c5uoSh/btsLFdlWV7T/XqDHBYlK20vIDllEx8Fkb1/img.png)
JVM (Java Virtual Machine)
- Write once, Run everyWhere
- Java 프로그램 실행을 담당하는 가상의 컴퓨터로, Java 바이트 코드 실행, 메모리 관리, 가비지 컬렉션 등을 담당한다.
- 자바와 운영체제 사이에서 중계자 역할을 하며, 자바가 운영체제에 구애받지 않고 프로그램을 실행할 수 있도록 도와준다.
JRE (Java Runtime Environment)
- Java 프로그램 실행을 위한 환경을 제공하며, JVM과 Java 클래스 라이브러리가 포함된다.
- JRE만 있으면 Java 프로그램을 실행할 수 있지만, Java 프로그램을 개발하기 위해서는 JDK가 필요하다.
- JDK 를 설치하면 JRE도 같이 설치된다. (JDK = JRE + a)
JDK (Java Development Kit)
- Java 애플리케이션을 개발하기 위한 도구 모음으로 JRE와 함께, 컴파일러, 디버거, 기타 개발 도구가 포함된다.
- Java 프로그램을 개발, 컴파일, 실행할 수 있는 모든 것을 제공한다.
- JDK를 이용하여 Java 코드를 작성하고, 이를 실행하기 위해 JRE를 사용한다.
JVM 특징
- 컴파일된 바이트 코드를 기계어로 변환
- 스택 기반의 가상 머신
- 메모리 관리와 GC 수행
C는 바로 기계어로 컴파일하기 때문에, HW 기종에 맞게 각각 컴파일 되어야 한다.
반면, 자바 프로그램은 중간 단계 언어( *.class )로 컴파일 하고, JVM이 각 OS에 설치되어 있다면 HW 기종에 관계 없이 한 번만 컴파일 하면 된다.
바이너리 코드/ 바이트 코드/ 기계어 정리
바이너리 코드
컴퓨터가 인식할 수 있는 0과 1로 구성된 이진코드
모든 바이너리 코드가 기계어는 아니다.
바이트 코드
가상머신이 이해할 수 있는 0, 1로 구성된 이진 코드
고급 언어로 작성된 코드가 컴파일되어 생성된다.
플랫폼에 독립적이며, 어떤 플랫폼에서든 JVM이 있는 한 실행할 수 있다.
CPU에서 직접 실행되지 않고, JVM에 의해 해석되거나, JIT 컴파일러에 의해 바이너리 코드로 변환된다.
기계어
CPU가 직접 해독하고 실행할 수 있는 특정한 바이너리 코드
특정 CPU 아키텍처에 종속적이며, CPU 제조사가 정의한 명령어 집합에 따라 달라진다.
모든 기계어가 이진코드 인 것은 아니며, 기계어는 특정 언어가 아니다.
즉, 소스 파일(.java)은 자바 클래스 파일(.clss)로 컴파일 된다.
JVM이 클래스파일(*.class)을 OS에 맞는 기계어로 변환(인터프리터와 JIT 컴파일러를 통해)하여 실행된다.
Java 실행 과정
![](https://blog.kakaocdn.net/dn/cGsE9J/btsLEZnSlaj/T5iqt3hSD24h2LAtmskzI1/img.png)
(이 과정에서 JIT 컴파일러 사용하여 성능을 향상시킨다.)
- 프로그램이 종료되거나 JVM이 종료될 때, JVM은 사용한 리소스를 해제하고 종료한다.
JVM 구성요소
![](https://blog.kakaocdn.net/dn/bkmj9A/btsLFCZ3R2p/iIvHbsEsKo4ftUn5mDhgw0/img.png)
JVM은 크게 아래과 같이 구성되어 있다.
- Class Loader
- Runtime Data Area
- Method Area
- Heap Area
- PC register
- Stack Area
- Native Method Stack
- Execution Engine
- Interpreter
- JIT Compiler
- Garbage Collector
Class Loader System
![](https://blog.kakaocdn.net/dn/cMsviG/btsLFQcGl5I/W13ATp2hDKBcJyNbnV5ug1/img.png)
자바 클래스 파일을 실행 시점(Runtime)에 읽어 메모리(Runtime Data Area)에 로드하고 실행한다.
클래스 로더는 JVM의 일부분으로, JVM이 실행되는 동안 메모리 내에 존재한다.
자바 프로그램 실행 중 필요한 클래스를 동적으로 로드하고, 연결하고 초기화한다. 자바의 클래스들은 한 번에 모든 클래스가 메모리에 올라가지 않고, 필요할 때 메모리에 올라간다.
로딩 → 링크 → 초기화
Loading
클래스 로더는 클래스 패스에서 클래스 파일을 찾아 메모리에 로드한다.
이때 클래스 로더는 바이트코드로 된 클래스 파일을 바이너리 형식으로 읽어, JVM의 메서드 영역에 저장한다.
로딩이 왼료되면, 해당 클래스 타입의 Class 객체가 생성되어 Heap 영역에 저장되고, 클래스 객체는 해당 클래스에 대한 메타데이터(클래스, 인터페이스, Enum 등)와 정보를 포함한다.
한번에 메모리에 모두 로드하지 않고, 필요한 경우 동적으로 메모리에 로드한다.
1. Bootstrap Class Loader
JVM의 핵심 클래스 로더로 rt.jar와 같은 핵심 자바 API를 로드한다.
네이티브 코드로 구현되어 있으며, Java로 접근할 수 없고 가장 높은 우선순위를 갖는다.
2. Extension Class Loader
lib/ext 디렉터리나 java.ext.dirs 시스템 속성에 지정된 위치에서 확장 클래스들을 로드한다.
부트스트랩 클래스 로더 다음의 계층에 위치하며, 사용자 정의 클래스 로더보다 우선적으로 사용한다.
3. Application Class Loader
애플리케이션 클래스패스(classpath)에 지정된 클래스, 애플리케이션에서 작성한 클래스와 라이브러리를 로드한다.
자바 응용 프로그램의 기본 클래스 로더로, 대부분의 사용자 클래스와 라이브러리를 로드하는데 사용된다.
Linking
로드된 클래스 파일을 JVM이 사용할 수 있도록 준비하는 과정
- Verify -> Prepare -> Resolve
1. Verification
.class 파일 형식이 유효한지 검사한다.
로드된 클래스가 자바 언어 명세에 부합한지 확인하고, 유효하지 않은 경우 런타임 에러(java.lang.VerifyError)가 발생 한다.
2. Preparation
클래스 변수(static 변수)와 같은 메모리를 할당하고 초기화한다.
3. Resolution
클래스의 심볼릭 메모리 레퍼런스를 메모리 영역에 존재하는 실제 메모리 주소로 변경한다.
* 심볼릭 메모리 레퍼런스
클래스의 특정 메모리 주소를 참조 관계로 구성한 것이 아닌 참조 대상의 이름만 갖는 것으로, 실제 메모리 주소가 아닌 이름만 갖는다.
Initialization
클래스 변수의 초기화 코드가 실행된다. static 초기화 블록이 실행되며, 변수가 초기화된다.
링크의 prepare 단계에서 확보한 메모리 영역에 클래스 변수들 (= static 변수)를 적절한 값으로 초기화한다.
Using
초기화된 클래스는 이제 JVM에서 사용될 준비가 되어 있으며, 인스턴스 생성이나 메서드 호출과 같은 작업이 가능하다.
Unloading
클래스 로더가 클래스를 메모리에서 제거한다.
JVM이 필요하지 않은 클래스를 GC를 통해 언로드한다.
Runtime Data Area
![](https://blog.kakaocdn.net/dn/sJ8TT/btsLGliXeM7/Tkbqi9itF910NM9gig0Be1/img.png)
JVM이 실행되기 위해 운영체제로 부터 할당받는 메모리 영역으로, 데이터 저장, 스레드 관리, 메서드 실행, 객체 관리 등을 수행한다.
프로그램 실행 시, 메서드 호출에 따라 스택 프레임이 생성되고, 메서드 호출이 완료되면 해당 스택 프레임이 제거된다. 객체는 힙에 동적으로 생성되며, 가비지 컬렉션을 통해 메모리를 관리한다.
클래스는 처음 참조될 때 메서드 영역에 로드되며, 이후 해당 클래스 정보는 메서드 영역에서 재사용 된다.
Method Area
JVM이 시작될 때 생성되며, 모든 스레드가 공유하는 영역으로 스태틱 영역(정적 변수들이 저장되는 공간)이 포함된다.
클래스정보, 변수정보, Method, static 변수, 상수풀 등이 저장된다.
Heap Area
모든 스레드에 공유되는 영역으로, new 명령어로 생성된 인스턴스와 객체가 저장된다.
JVM에서 가장 큰 메모리 영역으로 GC의 대상이 된다.
PC Register
스레드가 현재 실행 중인 명령어의 주소를 추적하는 역할을 한다.
JVM은 스택 기반의 가상 머신으로, 모든 명령어 실행은 스택 프레임을 통해 이루어진다.
메서드 호출 시, 새로운 스택 프레임이 생성되고 그 안에서 PC register이 업데이트되어 명령어를 실행한다. 명령어가 실행된 후 PC register은 다음 명령어 주소를 가리킨다. 메서드 호출이 완료되면 해당 스택 프레임이 제거되고, PC 레지스터는 메서드가 호출되기 전의 상태로 복원된다. 이 과정을 통해 실행 흐름을 관리한다.
Stack Area
각 스레드마다 하나씩 생성되는 영역으로, 호출된 메서드의 파라미터, 지역 변수, 리턴 값과 연산 값 등이 저장된다.
메서드가 호출 시 스택 프레임이 새로 생성되고 메서드 실행이 종료되면 삭제된다.
스택 오버플로우 발생시 JVM은 해당 스레드에 대해 예외를 발생시킨다.
구성요소스택 프레임
- 각 메서드 호출마다 스택 프레임이 생성되며, 메서드 실행이 끝나면 제거된다.
- 각 스택 프레임은 메서드 호출에 대한 정보를 담는다.지역 변수 배열
- 메서드에서 선언된 지역 변수와 매개 변수 저장오퍼랜드 스택
- 메서드 실행 중에 연산에 필요한 값을 저장하는 공간프레임 데이터
- 메서드의 실행 상태 저장(메서드 호출자, 리턴 주소 등)
Native Method Stack
각 스레드마다 하나씩 생성되는 영역으로 Java 외의 언어(C, C++)로 작성된 메서드의 정보가 저장된다.
Kernel 이 자체적으로 Stack 을 잡아 독자적으로 프로그램을 실행시킨다.
자바 코드 내에서 네이티브 메서드 호출 시 JVM은 네이티브 메서드를 실행하기 위해 네이티브 메서드를 호출할 수 있는 환경을 준비해야 하는데, 이때 Native Method Stack이 사용된다.
자바 메서드가 네이티브 메서드를 호출하면, Native Method Stack에 네이티브 메서드의 실행 정보와 로컬 변수를 저장하고, 네이티브 메서드 실행이 끝나면 스택에서 제거된다.
Execution Engine
![](https://blog.kakaocdn.net/dn/baos5p/btsLGgaTsll/w12vFutXH0i4mzvoPHwqtk/img.png)
Runtime Data Area에 적재된 자바 바이트 코드를 명령어 단위로 읽어 해석한다.
각 바이트 코드는 JVM이 이해할 수 있는 명령어로 반환되어, CPU에 의해 실행되고 수행된다.
1. 인터프리터
바이트 코드를 한 줄씩 읽어 실시간으로 해석하고 실행하기 때문에 빠르지만, 성능 면에서는 비효율적일 수 있다.
속도를 계선하기 위해 중복된 바이트 코드는 JIT 컴파일러를 사용한다.
2. JIT Compiler (Just-In-Time)
인터프리터의 단점을 개선하기 위해 등장, 실행 속도의 최적화
반복적으로 호출되는 바이트코드 블록을 식별하고, 이를 네이티브 머신 코드로 컴파일하여 성능을 향상 시킨다.
자바 바이트코드를 프로그램 실행 시점에 네이티브 코드로 변환하여 실행 속도를 높인다. 변환된 네이티브 코드는 JVM 내부 캐시에
저장되어 이후 호출 시 재사용되기 때문에 인터프리터가 더 이상 컴파일하지 않아도 된다.
→ 자바 바이트코드를 네이티브 코드로 변환하는 이유
네이티브 코드란 CPU가 직접 실행할 수 있는 기계어로, 운영체제와 하드웨어에 최적화된 코드이기 때문에 실행이 빠르다.
JVM은 바이트코드를 직접 실행하거나, JIT 컴파일러를 통해 바이트코드를 네이티브 코드로 변환하여 실행한다.
바이트 코드를 네이티브 코드로 변환하면 CPU가 바로 실행할 수 있어 성능이 향상된다.
→ 변환 과정
1. JVM이 바이트 코드를 읽는다. 처음에는 인터프리터가 바이트코드를 한 줄씩 해석하며 실행한다.
2. 이때, 자주 호출되는 메서드나 코드(핫스팟)를 JIT 컴파일러가 감지하고, 이를 네이티브 코드로 변환한다.
3. 변환된 네이티브 코드를 직접 실행한다.
→ 그냥 바이트 코드를 다 네이티브 코드로 바꾸면?
자바의 장점은 플랫폼에 독립적인 것인데, 네이티브 코드는 특정 운영 체제와 하드웨어 의존한다.
만약 모든 바이트코드를 네이티브 코드로 변환하게되면, 특정 플랫폼에서만 동작하게 되기 때문에 독립성이 사라진다.
그리고 네이티브 코드는 바이트코드보다 크기가 크기 때문에, 메모리 사용량이 증가할 수 있다.
→ 동적 최적화
실행 중 코드를 분석해 성능을 향상 시키는 기법이다. 프로그램이 실행되는 동안 실제 사용 패턴을 분석하여 최적화를 적용하기 땓문에
메모리 사용량과 CPU 리소스를 절약할 수 있다.
3. Garbage Collections
가비지 컬렉션을 관리하여 힙 메모리에서 더 이상 사용되지 않는 객체를 자동으로 정리한다.
'Java' 카테고리의 다른 글
GC(Garbage Collection)에 대하여 (2) | 2025.01.09 |
---|---|
자바의 실행과 JIT(Just-In-Time) 컴파일러 (1) | 2025.01.08 |
SQL Injection (0) | 2025.01.07 |
HashMap의 구조 (0) | 2025.01.07 |
Statement와 PreparedStatement (0) | 2025.01.07 |