[자바] | JVM이란?

제롬·2022년 3월 28일
0

JVM이란?

JVM이란 JavaVirtualMachine, 자바 가상 머신을 줄여 부르는 용어이다.
Java는 OS에 종속적이지 않는 특성을 갖는다. OS에 종속받지 않고 실행되기 위해서는 OS위에서 Java를 실행시킬 무언가가 필요하다. 이런 역할을 해주는것이 JVM이다.
즉, OS에 종속받지 않고 Java를 실행하게 해주는 가상 컴퓨터를 말한다.

JVM 특징

  1. 스택 기반의 가상 머신: 인텔이나 ARM 아키텍처같은 하드웨어가 레지스터 기반인데 반해 JVM은 스택 기반으로 동작한다.

  2. 심볼릭 레퍼런스: 기본 자료형(primitive data type)을 제외한 모든 타입(클래스와 인터페이스)을 명시적인 메모리 주소 기반의 레퍼런스가 아니라 심볼릭 레퍼런스를 통해 참조한다.

심볼릭 레퍼런스 : 참고하는 특정 클래스의 특정 메모리 주소를 참조 관계로 구성하지 않고 이름만 가지고 있는 것.

  1. 메모리 관리, 가비지 컬렉션(garbage collection) 역할 수행: 클래스 인스턴스는 사용자 코드에 의해 명시적으로 생성되고 가비지 컬렉션에의해 자동으로 파괴된다.

  2. 기본 자료형을 명확하게 정의하여 플랫폼 독립성을 보장: C/C++등 전통적인 언어는 플랫폼에 따라 int 자료형의 크기가 변하지만 JVM은 기본 자료형을 명확하게 정의하여 호환성을 유지하고 플랫폼 독립성을 보장한다.

  3. OS에 독립적이다.
    Java 소스코드(.java)는 기계(컴퓨터)가 인식할 수 있는 기계어로 컴파일해줘야 한다. 하지만 Java의 소스코드는 중간에 JVM이라는 가상머신을 거쳐서 OS에 도달하기 때문에 OS가 인식하는 기계어로 바로 컴파일 되는것이 아니라 JVM이 인식할 수 있는 바이트코드(.class)로 먼저 변환된다.
    변환된 바이트 코드는 JVM에 의해 OS가 인식할 수 있는 기계어로 변환된다. 이런 이유로 자바 언어로 만든 프로그램은 OS에 종속적이지 않고 어느 디바이스 위에서든지 JVM 위에서 실행이 가능한 것이다.

❗자바 컴파일러(Java Compiler)
자바 컴파일러는 JDK를 설치하면 bin 폴더안에 존재하는 javac.exe 파일을 말한다. 즉, JDK에 자바 컴파일러가 포함되어 있다.
자바 컴파일러는 .java 파일을 .class 라는 바이트코드 로 변환해준다.
(참고로 javac 명령어를 활용해 .java 파일을 .class 파일로 컴파일 할 수 있다.)

JVM이 코드를 변환하는 이유는?

일반 애플리케이션 코드는 OS만 거치고 하드웨어로 전달된다.

반면, JavaJVM을 한번 더 거쳐 전달될 뿐만 아니라 하드웨어에 맞게 완전히 컴파일된 상태가 아니라 실행시에 해석(interpret)되기 때문에 상대적으로 다른 언어에 비해 속도가 느리다.

하지만, 요즘에는 바이트코드(컴파일된 자바코드)를 하드웨어의 기계어로 바로 변환해주는 JIT 컴파일러와 같이 향상된 최적화 기술이 적용되어 속도의 격차를 많이 줄였다.

또한, JVM이 없다면 애플리케이션은 OS에 종속적이게 된다.

그래서 다른 OS에서 애플리케이션을 실행시키기 위해서는 애플리케이션을 그 OS에 맞게 변경해야 한다. 하지만 JVM을 사용하는 자바 애플리케이션의 경우 JVM하고만 상호작용을 하기 때문에 OS가 달라지더라도 프로그램의 변경없이 실행이 가능하다.

바이트 코드란?

가상 컴퓨터에서 돌아가는 실행 프로그램을 만들기 위한 이진 표현법이다.
자바 바이트 코드는 JVM이 이해할 수 있는 언어로 변환된 자바 소스코드(.java)를 의미한다.
자바 바이트 코드는 다시 실시간 번역기 또는 JIT 컴파일러에 의해 바이너리 코드로 변환된다.

❗바이너리 코드란?
바이너리 코드 또는 이진코드라고 한다.
기계(컴퓨터)가 인식할 수 있는 0과 1로 구성된 코드를 말한다.

❗기계어란?
0과 1로 이루어진 바이너리 코드이다.
기계어가 이진코드로 이루어졌을 뿐 모든 이진코드가 기계어는 아니다.
기계어는 특정한 언어가 아니라 CPU가 이해하는 명령어 집합이며, CPU 제조사마다 기계어가 다를 수 있다.

즉, CPU가 이해하는 언어는 바이너리 코드 그리고 JVM이 이해하는 언어는 바이트 코드이다.

JVM 구성

JVM은 크게 아래와 같이 구성되어 있다.

  • 클래스 로더(Class Loader)
  • 실행 엔진(Execution Engine)
    • 인터프리터(Interpreter)
    • JIT 컴파일러(Just-In-Time)
    • 가비지 컬렉터(Garbage Collector)
  • 런타임 데이터 영역(Runtime Data Area)

[클래스 로더]

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

[실행 엔진]

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

[인터프리터]

실행 엔진은 자바 바이트 코드를 명령어 단위(한줄씩)로 읽어서 실행한다. 하지만 이 방식은 한줄씩 수행하기 때문에 느린 인터프리터 언어의 단점을 그대로 갖고있다.

[JIT 컴파일러]

JIT 컴파일러는 프로그램을 실제 실행하는 시점에 바이트 코드를 기계어로 번역해준다.
JIT 컴파일러는 인터프리터 방식을 보완하기 위해 도입되었으며 인터프리터 방식으로 실행하다 적절한 시점에 바이트코드 전체를 컴파일하여 네이티브 코드로 변경한다. 이후에는 더이상 인터프리팅하지 않고 네이티브 코드로 직접 실행하는 방식이다. 네이티브 코드는 캐시에 보관하기 때문에 한번 컴파일된 코드는 빠르게 수행하게 된다.
단, JIT 컴파일러가 컴파일하는 과정은 바이트 코드를 인터프리팅하는 것보다 훨씬 오래걸린다. 따라서 한번만 실행되는 코드라면 인터프리팅하는것이 좋다.
따라서 JIT 컴파일러를 사용하는 JVM은 내부적으로 해당 메서드가 얼마나 자주 수행되는지 체크하고, 일정 정도를 넘을때만 컴파일을 수행한다.

[가비지 컬렉터]

시스템에서 더이상 사용하지 않는 동적 할당된 메모리 블럭을 찾아 자동으로 다시 사용가능한 자원으로 회수하는 것으로 시스템에서 가비지 컬렉션(Garbage Collection)을 수행하는 부분을 가비지 컬렉터라고 한다.

[런타임 데이터 영역]

프로그램을 수행하기 위해 OS에서 할당받은 메모리 공간

런타임 데이터 영역(Runtime Data Area) 구성

런타임 데이터 영역(Runtime Data Area)은 크게 아래와 같이 구성되어 있다.

  • Thread
    • PC (PC Register)
    • JVM Stack
    • Native Method Stack
  • Heap
  • Method Area
    • Rntime Constant Pool

[PC Register]

스레드(Thread)가 시작될때 생성되는 공간으로 스레드마다 하나씩 존재한다. 스레드가 어떤 부분을 어떤 명령으로 실행해야할 지에 대한 기록을 하는 부분으로 현재 실행중인 JVM 명령의 주소를 갖는다.

❗프로세스(Process)란?
단순히 실행중인 프로그램으로 사용자가 작성한 프로그램이 운영체제에 의해 메모리 공간을 할당받아 실행 중인 것을 말한다. 이러한 프로세스는 프로그램에 사용되는 데이터와 메모리 등의 자원 그리고 스레드로 구성된다.

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

[JVM 스택 영역]

프로그램 실행과정에서 임시로 할당되었다가 메소드를 빠져나가면 바로 소멸되는 특성의 데이터를 저장하기 위한 영역이다.
각종 형태의 변수나 임시 데이터, 스레드나 메서드의 정보를 저장한다.
메서드 호출시마다 각각의 스택 프레임(그 메서드만을 위한 공간)이 생성된다. 메서드 생성이 끝나면 프레임별로 삭제를 한다.
메서드 안에서 사용되는 값들을 저장한다. 또 호출된 메서드의 매개변수, 지역변수, 리턴 값 및 연산시 일어나는 값들을 임시로 저장한다.

[Native Method Stack]

자바 프로그램이 컴파일되어 생성되는 바이트코드가 아닌 실제 실행할 수 있는 기계어로 작성된 프로그램을 실행시키는 영역이다.
Java가 아닌 다른 언어로 작성된 코드를 위한 공간이다.
Java Native Interface를 통해 바이트 코드로 전환하여 저장하게 된다.
일반 프로그램처럼 커널이 스택을 잡아 독자적으로 프로그램을 시작시키는 영역이다. 이 부분을 통해 C code를 실행시켜 커널에 접근할 수 있다.

[Method Area(=Class Area, Stack Area)]

클래스 정보를 처음 메모리 공간에 올릴때 초기화하는 대상을 저장하기 위한 메모리 공간이다.
올라가는 메서드의 바이트코드는 프로그램의 흐름을 구성하는 바이트코드이다. 자바 프로그램은 main 메서드의 호출에서부터 계속된 메서드의 호출로 프로그램의 흐름을 이어가기 때문이다. 대부분 인스턴스의 생성도 메서드 내에서 명령하고 호출한다. 사실상 컴파일된 바이트코드의 대부분이 메서드 바이트코드이기 때문에 거의 모든 바이트코드가 올라가도 된다고 봐도된다.
이 공간에는 Runtime Constant Pool이라는 별도의 관리 영역도 함께 존재한다. 이는 상수 자료형을 저장하여 참고하고 중복을 막는 역할을 수행한다.

[Method Area(Static)에 저장되는 데이터의 종류]

  • 멤버변수(field information)
    • 멤버변수의 이름, 데이터 타입, 접근 제어자에 대한 정보
  • 메서드 정보(method information)
    • 메서드의 이름, 리턴타입, 매개변수, 접근제어자에 대한 정보
  • 타입(type information)
    • class/interface 여부 저장, type의 속성, 전체 이름, super class의 전체 이름(interface이거나 object인 경우 제외 이건 Heap에서 관리)

[Heap 영역]

객체를 저장하는 가상 메모리 공간이다. new 연산자로 생성된 객체와 배열을 저장한다.
단, Class Area 영역에 올라온 클래스들만 객체로 생성이 가능하다.
힙은 크게 3부분(New/Young, Permanent, Tenured)으로 나눌 수 있다.

[Permanent 영역]

생성된 객체 정보의 주솟값이 저장된 공간이다. 클래스 로더에 의해 로드되는 클래스, 메서드 등에 대한 메타(Meta)데이터가 저장되는 영역이고 JVM에 의해 주로 사용된다.
Reflection을 사용하여 동적으로 클래스가 로딩되는 경우에 사용된다.
내부적으로 Reflection 기능을 자주 사용하는 Spring Framework의 경우 이 영역에 대한 고려가 필요하다.

❗Reflection이란?
객체를 통해 클래스 정보를 분석해 내는 프로그래밍 기법이다.
구체적인 클래스 타입을 알지 못해도, 컴파일 된 바이트 코드를 통해 역으로 클래스 정보를 알아낼 수 있다.

[New/Young 영역]

New/Young Generation영역은 객체들이 최초에 생성되는 공간이다.
객체는 최초 Eden에서 생성되어 점차 Survivor0 그리고 survivor1로 순차적으로 이동한다.

Eden : 객체들이 최초로 생성되어 저장되는 공간
Survivor 0/1 : Eden에서 참조되는 객체들이 저장되는 공간

❗New/Young 영역의 GC
New/Young 영역의 Eden에서 생성된 객체들이 점차 쌓이면 Eden 영역에서 GC가 일어나게 된다. 이후 살아남은 객체들은 Survivor0영역으로 이동하게 되고 마찬가지로 Survivor0 영역에서 GC가 일어나 제거된 객체들을 제외하고 나머지 객체들은 Survivor1 영역으로 이동하게 된다. 이때 NEW/Young 영역에서 일어나는 GC를 Minor GC라고 한다.

[Old 영역]

New/Young 영역의 Minor GC에서 살아남은 객체들이 저장되는 공간이다.

❗Old 영역의 GC
이곳에 저장된 객체들은 New/Young 영역의 GC에서 살아남아 넘어온 생명주기가 긴 오래된 객체들이며 이들은 추후 GC가 발생해 사라지게 된다.
이곳에서 발생하는 GC를 Major GC라고 한다.
Old영역은 Minor영역에 비해 매우 큰 공간이기 때문에 Major GC는 Young 영역에서 발생하는 Minor GC에 비해 데이터를 지우는데 많은 비용(시간)이 들어간다.

JVM 자바 프로그램 실행과정

  1. 자바 프로그램이 실행되면 JVM은 OS로부터 프로그램 실행에 필요한 메모리를 할당받는다. JVM은 이 메모리를 용도에 따라 여러 영역으로 나누어 관리한다.
  2. 자바 컴파일러(javac)가 자바 소스코드(.java)를 읽어들여 자바 바이트코드(.class)로 변환시킨다.
  3. 클래스 로더(Class Loader)를 통해 클래스 파일(.class)을 JVM으로 로딩한다.
  4. 로딩된 클래스 파일은 실행 엔진(Execution Engine)을 통해 해석된다.
  5. 해석된 바이트코드는 Runtime Data Area에 배치되어 실질적인 수행이 이루어진다.
  6. 위 실행과정속에서 JVM은 필요에 따라 스레드 동기화(Thread Synchronization)와 GC같은 작업을 수행한다.

정리

JVM은 자바 바이트 코드(.class)파일을 OS에 맞게 해석해주는 역할을 한다. JVM이 해당 OS에 맞춰 바이트 코드 파일을 해석해 컴퓨터가 읽을 수 있는 코드로 변경해주기 때문에 자바는 OS에 독립적인 특성을 가질 수 있다.


Reference

Catsbi'DLog
_Jbee - 자바가상머신
doozi- JVM이란?
링크텍스트

0개의 댓글