새소식

Java

[Java] JVM 이란

  • -
728x90
Java 의 실행방식과 동작원리, 그에 기반이 되는 JVM 에 대해 파헤쳐보자.

 

Intro 

Java 를 사용는데 있어서 어떤 방식으로 실행되고 그에 사용되는 것은 무엇인지,  그 기반에 대해 알아야 더욱 이해에 대한 깊이를 더 할 수 있을 것 같다는 생각으로 이 글을 작성한다.

 

JVM, Java Virtual Machine

 jvm 을 탐구하기 이전에 가상화에 대해 먼저 알아두면 좋을 것 같아서 정리했다. 

 

가상화 (Virtualization) 란 컴퓨터 리소스의 추상화, 물리적인 컴퓨터 리소스의 특징을 다른 시스템, 응용 프로그램, 최종  사용자들이 리소스와 상호작용 하는 방식으로 감추는 기술이다.

 

💡가상머신, 하이퍼바이저, 컨테이너

 

가상머신(가상화 라는 용어 사용)
        - 단일 하드웨어에 여러 컴퓨팅 환경을 만드는 데 사용
        - 각 VM이 서로 다른 운영 체제를 실행하고 별도의 컴퓨터처럼 작동
하이퍼바이저
        - 가상 머신과 기본 하드웨어 사이에 위치하는 소프트웨어
        - 하드웨어 리소스를 분할해서 각 VM에 할당
        - 독립적인 운영 체제를 사용하여 가상 머신을 생성
        - 각 가상머신에는 독립적인 OS 존재
컨테이너(컨테이너화 라는 용어 사용)
        - 모두 동일한 호스트 운영 체제를 공유(커널 공유)하며 다른 컴퓨팅 환경을 가진것처럼 실행 가능
        - 가상머신에 비해 저렴하고 가벼움 (쓸데없는 코드의 복붙 없기 때문)

 

 JVM, Java Virtual Machine 은 직역하면 '자바를 실행하기 위한 가상 기계(컴퓨터)' 라고 할 수 있다.

Java 는 OS에 종속적이지 않다는 특징을 가지고 있다. CPU나 운영 체제의 종류와 무관하게 동일하게 동작하기 위해선 OS 위에서 Java 를 실행시킬 무언가가 필요하고 그서이 바로 JVM이다. WORA(write once run anywhere) 를 구현하기 위해 물리적인 머신과 별개의 가상 머신을 기반으로 동작하도록 설계되어 있고, 자바 바이트 코드를 실행하고자 하는 모든 하웨어에 JVM 을 동작시킴으로써 자바 실행 코드를 변경하지 않고도 모든 종류의 하드웨어에서 동작되게 한다.

 

 

컴파일 과정

 

Java 소스코드, 즉 원시코드(*.java)는 CPU가 인식을 하지 못하므로 기계어로 컴파일을 해줘야한다.

하지만 Java는 이 JVM 이라는 가상머신을 거쳐서 OS에 도달하기 때문에 OS가 인식할 수 있는 기계어로 바로 컴파일 되는게 아니라 JVM이 인식할 수 있는 Java bytecode(*.class)로 변환된다.

 

이렇게 JAVA 언어로 작성한 소스파일은 바로 운영체제로 가는 것이 아닌, "JVM을 거쳐서 운영체제와 상호작용"을 하는데,
이 때문에 개발자가 소스코드를 작성하는 것에 있어서 "운영체제로부터 독립적"일 수 있게 되기 때문에 사용한다.

 

컴파일

java Compiler 는 JDK 를 설치하면 javac.exe 라는 실행 파일 형태로 설치된다. 정확히는 JDK 의 bin 폴더에 javac.exe 로 존재한다.

java Compiler 의 javac 라는 명령어를 사용하면 .class 파일을 생성할 수 있다.

 

// .java 파일을 .class파일로 변환
$ javac Test.java 

// 조회
$ ls
Test.class  Test.java

// 실행
$ java Test

 

바이트 코드

가상 컴퓨터(VM) 에서 돌아가는 실행 프로그램을 위한 이진 표현법이다. 자바 컴파일러에 의해 번역된 코드(개발자가 이해하는 언어 -> JVM 이 이해하는 언어)이며 기계어가 아닌 한 단계 컴파일된 중간 단계 코드이다. 또한 자바 코드를 배포하는 가장 작은 단위이다.

 

JIT 컴파일러

JIT 컴파일(just-in-time compliation) 또는 동적 번역(dynamic translation) 이라고 한다. JIT 컴파일러는 프로그램을 실제 실행하는 시점에 기계어로 번역하는 컴파일러이다.

인터프리터 방식의 단점을 보완하기 위해 도입되었다. 인터프리터 방식으로 실행하다가 적절한 시점에 바이트 코드 전체를 컴파일하여 기계어로 변경하고, 이후에는 해당 더 이상 인터프리팅 하지 않고 기계어로 직접 실행하는 방식이다.

기계어(컴파일된 코드)는 캐시에 보관하기 때문에 한 번 컴파일된 코드는 빠르게 수행하게 된다. 물론 JIT 컴파일러가 컴파일하는 과정은 바이트 코드를 인터프리팅하는 것보다 훨씬 오래걸리므로 한 번만 실행되는 코드라면 컴파일 하지 않고 인터프리팅하는 것이 유리하다.

따라서 JIT 컴파일러를 사용하는 JVM들은 내부적으로 해당 메서드가 얼마나 자주 수행되는지 체크하고 일정 정도를 넣을때에만 컴파일을 수행한다. 자바에선 자바 컴파일러가 자바 프로그램 코드를 바이트 코드로 변환한 다음, 실제 바이트 코드를 실행하는 시점에서 자바 가상 머신(JVM, 정확히는 JRE)이 바이트 코드를 JIT 컴파일을 통해 기계어로 변환한다.

 

✳️ 인터프리터 방식 & Jit 컴파일 방식
인터프리트 방식 : 실행 중 프로그래밍 언어을 읽어가면서 해당 기능에 대응하는 기계어 코드를 실행
정적 컴파일 방식 : 실행하기 전 프로그램 코드 기계어로 번역Jit 컴파일은 인터프리트 방식과 정적 컴파일 방식의 혼합으로 jit 컴파일러는 실행 시점에 인터프리트 방식으로 실행하면서 그 기계어 코드를 캐싱해서 같은 함수를 여러 번 부르면 매번 해당 기계어 코드 생성하는 것을 방지한다.

 

JVM 의 특징

- 스택 기반의 가상 머신
    ↔ 레지스터 기반 (명령어 수 적고, 스택에 대한 오버헤드 없으나 명령어 커짐, 일반적으로 레지스터 기반 VM이 더 성능 좋음. but 레지스터 기반 VM은 하드웨어 스펙에 의존적)
- JVM은 여러 OS, 하드웨어에서 돌아가는 VM을 구현해야 하기 때문에 스택 기반일거라는 얘기가 있다.
- 가비지 컬렉션: 클래스 인스턴스는 명시적으로 생성, 가비지 컬렉션에 의해 자동파괴.
- 심볼릭 레퍼런스: class, field, method의 이름 등의 심볼릭한 이름을 resolution 단계에 실제 물리적 메모리 주소로 링킹
- 전통적인 언어는 플랫폼에 따라 int형 크기가 달라지기에 JVM은 기본 자료형을 명확히 정의해서 호환성 유지하고 플랫폼 독립성 보장
- 네트워크 바이트 오더: 빅 엔디안
    - 바이트를 어떻게 조합해서 숫자, 문자 등으로 나타낼지 규칙으로 정한 것
    - 빅엔디안은 상위 바이트부터 순차적으로 저장
- 위의 JVM 명세를 따르면 어떤 벤더든 JVM 개발해서 제공 가능
    - JVM명세를 만족하는지 여부는 TCK라는 테스트 툴을 통과해야 JVM으로 이름 붙일 수 있음

 

 

🧊 JVM 의 구조

JVM 의 구조

JVM 은 크게 아래와 같이 이루어져 있다.

 

1. 클래스 로더(Class Loader)
2. 실행 엔진(Execution Engine)
   - 인터프리터(Interpreter)
   - JIT 컴파일러(Just-in-Time)
   - 가비지 콜렉터(Garbage collector)
3. 런타임 데이터 영역 (Runtime Data Area)

 

이 구성 요소들은 서로 긴밀하게 연결되어 Java 애플리케이션의 효율적인 실행과 메모리 관리, 플랫폼 독립성을 보장하는 역할을 한다.

 

1. 클래스 로더

JVM 내로 클래스파일(*.class) 을 로드하고, 링크를 통해 배치하는 작업을 수행하는 모듈이다. 런타임시 동적으로 클래스를 로드하고 jar 파일 내 저장된 클래스들을 JVM 위에 탑재한다. 즉, 클래스를 처음으로 참조할 때, 해당 클래스를 로드하고 링크하는 역할을 한다.

 

🔻 동적 로드

- 자바는 컴파일 타임이 아닌 런타임에 클래스를 처음 참조할 때 해당 클래스를 로드하고 링크한다.

 

🔻 네임스페이스

- 로드된 클래스 보관

- 클래스를 로드할 때 이미 로드된 클래스인지 확인하기 위해서 네임스페이스에 보관된 FQCN(Fully Qualified Class Name) 을 기준으로 클래스를 찾는다. 같은 FQCN 이어도 네임스페이스가 다르면 다른 클래스로 간주한다.

 

💠 클래스 로드 단계

 

로드, 클래스를 파일에서 가져와서 JVM 의 메모리에 로드

검증, 읽어들인 클래스가 자바 언어 명세 및 JVM 명세에 명시된 대로 구성돼 있는지 검사

준비, 클래스가 필요로 하는 메모리 할당, 클래스에 정의된 필드, 메서드, 인터페이스들을 나타내는 데이터 구조 준비

분석, 클래스의 상수 풀 내 심볼릭 레퍼런스를 다이렉트 레퍼런스로 변경

초기화, 클래스 변수를 적절한 값으로 초기화

 

2. 실행 엔진

 

클래스를 실행시키는 역할이다. 클래스 로더가 JVM 내의 런타임 데이터 영역에 바이트 코드를 배치시키고, 이것은 실행 엔진에 의해 실행된다. 자바 바이트 코드(.class)는 기계가 바로 수행할 수 있는 언어보다는 비교적 인간이 보기 편한 형태로 기술된 것이다. 그래서 실행 엔진은 이와 같은 바이트 코드를 실제로 JVM 내부에서 기계가 실행할 수 있는 형태로 변경한다. 바이트 코드를 기계어로 변경하는 방식에는 2가지가 있고 아래와 같다.

 

🔻인터프리터

- 실행 엔진은 자바 바이트 코드를 명령어 단위로 읽어서 실행한다. 하지만 한 줄씩 수행하기 때문에 느리다는 단점이 있다.

 

🔻JIT(Just-In-Time)

- 인터프리터 방식으로 실행하다가 적절한 시점에 바이트 코드 전체를 컴파일하여 기계어로 변경하고, 이후에는 더 이상 인터프리팅 하지 않고 기계어로 직접 실행하는 방식

 

🔻 가비지 콜렉터

- 힙 메모리에서 더 이상 사용되지 않는 인스턴스를 찾아 메모리에서 제거하여 메모리를 관리한다.

 

3. 런타임 데이터 영역

Runtime Area

 

JVM 이라는 프로그램이 운영체제 위에서 실행되면서 할당받는 메모리 영역이다. 스레드마다 생성되는 pc register, jvm stack, mehtod stack 등으로 이루어져 있다.

 

🔻 프로그램 카운터 레지스터, PC Register

- 스레드가 시작될 때 생성되며 스레드별로 PC 레지스터가 존재한다. 현재 실행 중인 JVM 명령의 주소를 가리킨다.

 

🔻 스택, Stack

- 스레드가 시작될 때 생성. 각 스레드는 자신만의 스택을 가지고 있으며, 이곳에는 메소드 호출과 그 메소드의 로컬 변수, 매개 변수 등이 저장된다. 스택은 LIFO(Last In First Out) 방식으로 작동하며, 스레드가 종료되면 해당 스택도 제거된다.

 

🔻 메소드 영역, Method Area

- 모든 클래스 정보, 상수, 정젹 변수, JIT 컴파일러에 의해 컴파일된 코드와 같은 데이터를 저장한다. 이 영역은 JVM 당 하나만 존재하며 모든 스레드가 공유한다.

 

🔻 힙, Heap

- JVM 의 힙 영역은 인스턴스(객체)와 배열을 포함하는 런타임 데이터를 저장하는 곳이다. 이 영역은 가비지 컬렉션의 주요 대상이며, 모든 스레드에 의해 공유된다.

 

🔻 네이티브 메소드 스택, Native Method Stack

- JVM 외부의 네이티브 코드를 실행할 때 사용되는 스택이다. Java 가 아닌 다른 언어로 작성된 코드를 실행하는데 필요한 데이터를 저장한다.

 

💡 스레드(thread)란?
- 스레드(thread)란 프로세스(process) 내에서 실제로 작업을 수행하는 주체를 의미한다. 모든 프로세스에는 한 개 이상의 스레드가 존재하여 작업을 수행한다. 또한, 두 개 이상의 스레드를 가지는 프로세스를 멀티스레드 프로세스(multi-threaded process)라고 한다.

 

JDK 과 JRE의 차이

 

🔻 JDK, Java Development Kit (자바 개발 키트)
- Java 를 사용하기 위해 필요한 모든 기능을 갖춘 Java용 SDK (Software Development Kit)이다. JDK 는 JRE를 포함하고 있다. JRE에 있는 모든 것 뿐만 아니라 컴파일러(javac)와 jdb, javadoc 과 같은 도구도 있다. 즉, JDK는 프로그램을 생성, 실행, 컴파일할 수 있다.

🔻 JRE, Java Runtime Environment (자바 런타임 환경)
- JVM + 자바 클래스 라이브러리(Java Class Library) 등으로 구성되어 있다. 컴파일 된 Java 프로그램을 실행하는데 필요한 패키지이다.

✳️ 요약
JDK는 자바 프로그램을 실행, 컴파일, 개발용 도구. JRE, JVM를 모두 포함하는 포괄적이 키트이다. JRE는 자바 프로그램을 실행할 수 있게 하는 도구이다. JVM을 포함하고 있다.

💡 SDK란?
Software Development Kit (소프트웨어 개발 키트)
하드웨어 플랫폼, 운영체제 또는 프로그래밍 언어 제작사가 제공하는 툴이다. 키트의 요소는 제작사마다 다르다.
SDK의 대표적인 예로, JDK 등이 있다.
SDK를 활용하여 애플리케이션을 개발할 수 있다.

728x90
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.