JVM 탄생 계기

- 일반적으로 개발자가 프로그램을 만듦 → 프로그램은 운영체제를 통하여 해석되어 작동함
- 아쉽게도 운영체제는 이 세상 단 하나만 존재하지 않음 매우 다양함 (ex 윈도우 맥 리눅스)
- 그렇기에 개발자가 하나의 프로그램을 만들면 각각 다른 운영체제 환경에 맞게끔 프로그램을 고치거나 만들어야 함
- 비효율적이라고 생각하였고 그렇다면 프로그램을 한 번만 만들면 모든 운영체제 환경에서 동작할 수 있게 만들면 되지 않을까? → JVM 탄생 계기
위 그림과 같이 JVM은 컴퓨터 운영체제 위에 존재한다. 그래서 JAVA로 프로그램을 만들어서 JVM으로 보내면 JVM이 바이트 코드로 변환해주어 각각 다른 운영체제 환경에 맞게 변환해 준다.
“Write once. run anywhere” 한 번 작성하면 어디서든 실행된다. 그러므로 개발자는 효율적으로 개발을 할 수 있다. 근데 생각해 보면 JVM 입장에서는 JVM은 운영체제 맞게 환경이 구축되어야 하니 JVM은 운영체제에 종속적이다. 또한 JVM 통하여 코드를 변환하는 과정이 있다 보니 C나 C++ 보다 컴파일 속도가 느릴 수밖에 없다.
내부의 최적화된 JIT 컴파일러를 통해 속도의 격차를 많이 줄여다고는 하나 그래도 느리다고 한다.
정리
- JVM은 자바를 실행시키는 가상 운영체제라고 생각하자.
- Java는 플랫폼(운영체제)에 종속적이지 않지만 JVM은 플랫폼(운영체제)에 종속적이다.
JAVA 동작 원리

- . java 파일을 자바컴파일러(javac)로 JVM이 읽을 수 있는 자바 바이트 코드(. class)로 컴파일러 하여 파일을 만든다.
- 컴파일된 바이트 코드가 JVM 안 클래스 로더에게 전달된다.
- JVM의 클래스 로더는 동적 로딩(Dynamic Loading)을 통해 클래스들을 로딩 및 링크하여 런타임 데이터 영역(RunTime Data Area)이라는 곳으로 보낸다. 런타임 데이터 영역은 JVM이 운영체제로 부터 할당받은 메모리 영역이다.
동적 로딩(Dynamic Loading) 이란?
자바 애플리케이션은 여러 개의 객체가 서로 연결되어 실행되는데 이 객체들은 클래스로부터 생성된다. 애플리케이션이 실행될 때 모든 객체가 생성되지 않고 객체가 필요한 시점에 객체를 생성하는 데 이를 동적 로딩이라고 한다.
실행엔진(Execution Engine)은 JVM 메모리에 올라온 바이트 코드들을 명령어 단위로 하나씩 가져와서 실행한다.
이때, 실행엔진은 2가지 방식으로 동작한다.
- 인터프리터 방식 : 바이트코드를 기계어로 번역하는 임무를 맡는다. 바이트코드 명령어 단위로 하나씩 읽어서 실행한다. 명령어 하나하나 실행은 빠르나 전체적인 실행 속도는 느리다.
- JIT 컴파일러 : 이러한 초기 자바 인터프리터 방식을 보완하기 위한 도입된 방식이다. 바이트 코드를 JIT 컴파일러로 보내면 바이트코드를 실행하는 시점에 운영체제 맞는 Native Code로 변환하여 실행 속도를 개선시켰다. 하지만 이것 또한 변환할 때 과정에서 비용 들기 때문에 인터프리터 방식을 사용하다고 일정 수준이 넘으면 JIT 컴파일러를 사용한다고 한다.
JIT 컴파일러는 매번 같은 코드를 해석하지 않고 실행될 때 컴파일러 된 코드를 캐싱해 버린다. 이후에 변환된 코드만 해석하기에 인터프리터 방식보다 10배 ~ 20배 정도 빠르다고 한다.
JVM 동작원리 정리
. java 파일 → 자바 컴파일러 (javac) →. class 파일(java ByteCode) → 클래스 로더 → 런타임 데이터 영역 → 실행엔진
실행엔진은 2가지 방식이 존재 인터프리터 방식과 JIT 컴파일러 방식이 존재한다.
JVM을 좀 더 파헤쳐보자
Runtime Data Area (런타임 데이터 영역)
- JVM이 JAVA ByteCode를 실행하기 위한 메모리 공간이고, 런타임 영역은 크게 5가지로 나눌 수 있다.

Method Area
- 모든 스레드가 공유하는 영역으로 JVM 실행 시 각각 클래스에 모든 정보가 저장되는 공간이다. Method Area 영역 안에는 Runtime constant Pool라는 곳이 존재한다.
런타임 상수 풀(Runtime constant Pool)란?
• 메서드 영역 안에 포함되어 있고 독자적 중요성이 있다.
• 클래스와 인터페이스 상수, 메서드와 필드에 대한 모든 래퍼런스 정보를 담고 있다.
• JVM은 Runtime Constant Pool을 통해 해당 메서드나 필드의 실제 메모리 상 주소를 찾아 참조한다.
Heap
- 런타임 시 생성되는 모든 객체의 인스턴스가 저장되는 영역이다. 흔히 String이나 new 통해 만들어진 객체들에 인스턴스를 가지고 있다고 생각하면 된다. Heap은 가비지컬렉션에 대상이 되는 영역이기도 하다. 또한 모든 스레드가 공유하는 영역이기도 하다.
예를 들어 JVM이 생성된 객체인스턴스에 메서드를 호출하려는 순간 새롭게 인스턴스 생성하지 않고 Heap 영역안에 존재하는 인스턴스를 참조하여 메소드를 호출시키기에 메모리를 절약할 수 있다.
PC Register
- 현재 수행 중인 명령어 주소를 가지고 있으며 스레드가 시작할 때 생성되고 스레드 당 하나씩 존재한다.
JVM Stack
- 메서드가 실행하기 위한 정보들은 저장하는 영역이다. JVM Stack 내부에는 프레임(Frame) 이라는 자료구조가 존재하는데 메소드가 호출 될때 마다 하나씩 생성되어 Push 하고 메소드가 끝나거나 예외가 터지면 pop 한다. 스레드 당 하나씩 존재한다.
Native Method Stack
- JAVA ByteCode 이외 언어에 제공되는 메서드를 저장하고 있다. 바이트코드 전환 비용을 줄이기 위해 미리 메소드를 저장했다고 생각하면 된다.
'JAVA' 카테고리의 다른 글
| Optional 더 잘 사용해보기 (0) | 2023.04.10 |
|---|---|
| Java8 : Stream 사용법 (0) | 2023.03.31 |
| sigton Patten이란? (0) | 2023.03.31 |
| Garbeage Collection 이란? (0) | 2023.03.31 |
| Comparable 과 Comparator 차이점 (0) | 2023.03.30 |