JVM (Java Virtual Machine)

Victor·2025년 12월 18일

JVM

JVM의 JDK와 JRE 상관관계

JREJVM과 자바 앱 개발을 위한 표준 라이브러리를 포함한다.

JDKJRE 와 자바 파일을 컴파일, 조작 및 제어하는 여러 개발 툴을 포함한다.

JVM 이란

JVM은 Java 플랫폼의 초석이다. JDK의 일부분으로, 앱을 실행시키는 명령어 java이다. 하드웨어나 운영체제(OS)에 종속되지 않고 독립적으로 작동하며, 컴파일된 코드의 크기를 작게 유지한다. 또한 악성 프로그램으로부터 사용자를 보호하는 역할도 한다. 즉, JVM은 단지 실행뿐만 아니라 메모리 관리 등 많은 추가 작업을 진행하는 "관리형 런타임 환경"이다.

JVM의 기술적 특성

JVM은 명령어 집합을 가지고 있고, 실행 시점에 다양한 메모리 영역을 조작하는 추상적인 컴퓨터다. 특정 구현 기술이나 하드웨어에 얽매이지 않으며, 단순히 코드를 해석하는 방식에 그치지 않고 실리콘 CPU의 명령어로 컴파일하여 실행한다.

JVM은 Java 언어를 직접 알지 못한다. JVM은 오직 특정한 바이너리 형식인 .class 파일만 인식한다. 이 파일 안에는 JVM 명령어(자바 바이트코드)심볼 테이블 등이 들어 있다. 보안을 위해 JVM은 클래스 파일 내의 코드가 문법적, 구조적으로 제약 조건을 잘 지키고 있는지 엄격하게 검사한다. 따라서 Java가 아니더라도, JVM 명세에 맞는 .class 파일만 생성할 수 있다면 어떤 언어(예: Kotlin, Scala, Groovy 등)든 JVM 위에서 실행될 수 있다.

"Write once, run everywhere"를 실현하기 위해, 자바 프로그램을 실행시키려면 먼저 자바 코드를 자바 바이트코드로 변환해야 한다. 이때 빌드 툴인 Maven이나 Gradle을 통해 javac를 사용한다. 변환된 바이트코드는 JVM에서 실행되는데, 이 바이트코드가 서로 다른 OS와 CPU에서 실행되기 위한 각 플랫폼별 처리는 JVM 개발자들이 이미 해놓았다. 개발자는 단 하나의 코드베이스만 작성하면 모든 플랫폼에서 실행할 수 있다.

JVM 의 동작 방식 개요

  1. 컴파일러가 .java 파일을 .class 파일로 컴파일한다.
  2. .class 파일을 JVM 에 입력한다.
  3. .class 파일을 로드하고 실행한다.

런타임 데이터 영역

Java 가상 머신은 프로그램 실행 중에 사용하는 다양한 데이터 영역을 정의한다. 이 영역들은 크게 두 가지로 나뉜다.

  • JVM 단위 영역 (공유 영역): JVM이 시작될 때 생성되고, JVM이 종료될 때만 소멸한다. 모든 스레드가 공유하는 공간이다.
  • 스레드 단위 영역 (개별 영역): 각 스레드가 생성될 때 함께 만들어지고, 해당 스레드가 종료되면 소멸한다. 즉, 각 스레드만의 독립적인 공간이다.

PC 레지스터

Java 가상 머신은 동시에 여러 스레드를 실행할 수 있다. 각 스레드는 공유되지 않는 독자적인 PC 레지스터를 갖는다.

어느 시점이든, 각 스레드는 하나의 메서드를 실행하고 있는데, 이를 현재 메서드(Current Method)라고 한다. PC 레지스터는 이 메서드 안에서 어디를 실행 중인지 기록한다.

스레드가 만약 일반 Java 메서드를 실행하면 PC 레지스터에 현재 실행 중인 JVM 명령어 (Java 바이트코드)의 주소가 저장된다. 만약 Native 메서드 (C/C++ 등)를 실행한다면 Java 바이트코드가 아닌 외부 코드가 실행되므로, JVM PC 레지스터의 값은 정의되지 않는다(Undefined).

PC 레지스터의 크기는 특정 플랫폼(32비트 혹은 64비트)에서 returnAddress나 실제 메모리 포인터(Native Pointer)를 충분히 담을 수 있을 만큼 크게 설계되어 있다.

JVM 스택

각 자바 가상 머신 스레드는 스레드 생성과 동시에 만들어지는 전용 JVM 스택을 갖는다. JVM 스택은 프레임을 저장한다. 지역 변수와 중간 연산 결과를 보관하며 메서드 호출 및 복귀에 기여한다. JVM 스택은 프레임을 push하고 pop하는 동작 외에는 직접적으로 조작이 불가하여, 프레임은 heap에 할당될 수도 있다. JVM 스택을 위한 메모리가 반드시 연속적일 필요는 없다.

JVM 스택의 크기가 고정되거나 연산의 요구에 따라 동적으로 확장 및 축소되는 것을 모두 허용한다. 만약 JVM 스택이 고정된 크기라면, 각 스택이 생성될 때 그 크기를 독립적으로 선택할 수 있다.

JVM 구현체는 프로그래머나 사용자에게 JVM 스택의 초기 크기에 대한 제어권을 제공할 수 있으며, 스택이 동적으로 확장되거나 축소되는 경우에는 최대 및 최소 크기에 대한 제어권도 제공할 수 있다.

스레드에서의 연산에 허용된 것보다 더 큰 JVM 스택이 필요할 경우, 자바 가상 머신은 StackOverflowError를 던진다.

만약 JVM 스택을 동적으로 확장할 수 있는데 확장을 시도해도 이를 수행할 충분한 메모리를 확보할 수 없는 경우, 혹은 새로운 스레드를 위한 초기 JVM 스택을 생성할 때 메모리가 부족한 경우, 자바 가상 머신은 OutOfMemoryError를 던진다.

Heap

JVM은 모든 JVM 스레드 간에 공유되는 heap을 갖는다. 힙은 모든 클래스 객체와 배열을 위한 메모리가 할당되는 런타임 데이터 영역이다.

heap 은 가상 머신이 시작될 때 생성된다. 객체를 위한 heap 저장 공간은 garbage collector라고 알려진 자동 저장 관리 시스템에 의해 회수되며, 객체는 절대로 명시적으로 해제되지 않는다. JVM은 특정한 유형의 자동 저장 관리 시스템을 가정하지 않으며, 저장 관리 기법은 구현자의 시스템 요구사항에 따라 선택될 수 있다. heap 은 고정된 크기일 수도 있고, 연산의 요구에 따라 확장될 수 있으며, 더 큰 heap 이 필요하지 않게 되면 축소될 수도 있다. heap 을 위한 메모리가 반드시 연속적일 필요는 없다.

JVM 구현체는 프로그래머나 사용자에게 heap 의 초기 크기에 대한 제어권을 제공할 수 있으며, heap 이 동적으로 확장되거나 축소될 수 있는 경우에는 최대 및 최소 heap 크기에 대한 제어권도 제공할 수 있다.

연산 중에 자동 저장 관리 시스템이 제공할 수 있는 것보다 더 많은 heap 공간이 필요할 경우, JVM은 OutOfMemoryError를 던진다.

Method Area

JVM은 모든 JVM 스레드 간에 공유되는 메서드 영역을 갖는다. (1) 런타임 상수 풀, (2) 필드 및 메서드 데이터, (3) 메서드와 생성자의 코드와 같이 클래스별 구조들이 저장되며, (4) 클래스 및 인터페이스 초기화와 인스턴스 초기화에 사용되는 특수 메서드들도 포함된다.

메서드 영역은 가상 머신이 시작될 때 생성된다. 메서드 영역은 논리적으로 힙의 일부이지만, 단순한 구현체에서는 이를 가비지 컬렉션하지 않거나 압축하지 않는 것을 선택할 수도 있다. 메서드 영역의 위치나 컴파일된 코드를 관리하는 정책을 강제하지 않는다. 메서드 영역은 고정된 크기일 수도 있고, 연산의 요구에 따라 확장될 수 있으며, 더 큰 메서드 영역이 필요하지 않게 되면 축소될 수도 있다. 메서드 영역을 위한 메모리가 반드시 연속적일 필요는 없다.

JVM 구현체는 프로그래머나 사용자에게 메서드 영역의 초기 크기에 대한 제어권을 제공할 수 있으며, 크기가 가변적인 메서드 영역의 경우에는 최대 및 최소 크기에 대한 제어권도 제공할 수 있다.

메서드 영역의 메모리가 할당 요청을 충족시킬 수 없을 경우, JVM은 OutOfMemoryError를 던진다.

Native Stack

JVM 구현체는 네이티브 메서드(자바 언어 이외의 언어로 작성된 메서드)를 지원하기 위해 흔히 "C 스택"이라고 불리는 전통적인 스택을 사용할 수 있다. 네이티브 메서드 스택은 C와 같은 언어로 작성된 JVM 명령어 집합의 인터프리터 구현체에 의해서도 사용될 수 있다. 네이티브 메서드를 로드할 수 없거나 스스로 전통적인 스택에 의존하지 않는 JVM 구현체는 네이티브 메서드 스택을 제공할 필요가 없다. 네이티브 메서드 스택이 제공될 경우, 통상적으로 각 스레드가 생성될 때 스레드별로 할당된다.

네이티브 메서드 스택의 크기가 고정되거나 연산의 요구에 따라 동적으로 확장 및 축소되는 것을 모두 허용한다. 네이티브 메서드 스택이 고정된 크기라면, 각 스택이 생성될 때 그 크기를 독립적으로 선택할 수 있다.

JVM 구현체는 프로그래머나 사용자에게 네이티브 메서드 스택의 초기 크기에 대한 제어권을 제공할 수 있으며, 크기가 가변적인 네이티브 메서드 스택의 경우에는 최대 및 최소 크기에 대한 제어권도 제공할 수 있다.

스레드에서의 연산에 허용된 것보다 더 큰 네이티브 메서드 스택이 필요할 경우, JVM은 StackOverflowError를 던진다.

참고


The Anatomy of a JVM

JVM Architecture - Software Performance Engineering/Testing Notes

Chapter 2. The Structure of the Java Virtual Machine

0개의 댓글