JAVA Virtual Machine [JVM] 이란?
JVM이란 자바 가상 머신의 약자이다. 여기서 우선 가상 머신에 대한 개념을 먼저 알고 가야 한다.
Virtual Machine :컴퓨터 환경을 소프트웨어로 구현한 것으로, 컴퓨터를 에뮬레이션 하는 소프트웨어이다.
Emulator : 한 시스템에서 다른 시스템을 복제한다는 뜻
JVM의 역할
- JAVA Application을 Classloader를 통해 읽어 들여 Java API와 함께 실행
- JAVA와 OS 사이에서 중개자 역할을 수행하여 JAVA가 OS에 구애받지 않고 재사용을 가능하게 해 줌
- 메모리 관리
- Garbage Collection
- 스택 기반의 가상 머신 : 대다수의 명령어가 스택 선두에서 피연산자를 택하고 결과는 다시 스택에 넣는다.
Java Classloader
Java class를 JVM으로 동적 로드하는 자바 런타임 환경의 일부이다. 일반적으로 클래스들은 요청 시 한 차례만 로드된다.
자바 런타임 시스템은 클래스 로더 때문에 파일과 파일 시스템에 대해 알 필요가 없다.
JVM 학습의 필요성
한정된 메모리를 효율적으로 사용하여 최고의 성능을 내기 위해서 JVM을 학습해야 한다. 위에서 나온 JVM의 역할 중 가장 큰 역할이 바로 메모리 관리와 Garbage collection 기능이다. 이 부분에서 JVM의 동작원리를 이해하고 올바르게 사용하는 것이 JAVA를 이용하여 개발하는 개발자의 올바른 자세이다.
JVM 동작 순서
- 프로그램이 실행되면 JVM은 OS로부터 이 프로그램에 필요한 메모리를 할당받는다.
JVM은 이 메모리를 용도에 따라 여러 영역으로 나누어 관리한다. - JAVA Compiler(javac)가 JAVA code(.java file)를 읽어 들여 JAVA Byte code(.class file)로 변환시킨다.
- Class Loader를 통해 Class file들을 JVM으로 로딩한다.
- 로딩된 class파일들은 Excution engine을 통해 해석된다.
- 해석된 Byte code는 Runtime Data Areas에 배치되어 실질적인 수행이 이뤄지게 된다.
이러한 과정에서 JVM은 필요에 따라 Thread Synchronization과 GC 같은 관리 작업을 진행한다.
Thread Synchronization
Thread는 한 번에 여러 작업을 동시에 진행하는 것을 의미한다. 프로그램 내에서 두 개 이상의 Thread를 시작할 때, 여러 Thread가 동일한 리소스에 액세스 하려고 시도하고 마지막으로 동시성 문제로 인해 예기치 않은 결과가 발생할 수 있다. 예를 들어 여러 Thread가 동일한 파일 내에서 쓰려고 하면 Thread 중 하나가 데이터를 덮어쓸 수 있거나 한 Thread가 동시에 같은 파일을 여는 동안 다를 Thread가 같은 파일을 닫을 수 있기 때문에 데이터 손상이 일어날 수 있다.
이 문제점을 해결하기 위해 공유 데이터에 대하여 Thread들의 동시 접근을 방지하는 Thread Synchronization을 진행한다.
여러 Thread의 작업을 동기화하고 주어진 시점에서 하나의 Thread만 리소스에 액세스 할 수 있는지 확인해야 한다. 이 부분에서 monitors라는 개념을 사용하여 구현된다. Java의 각 객체는 Thread가 잠기거나 잠금을 해제할 수 있는 monitor와 연결된다. 이후 한 번에 하나의 Thread만 monitor에서 잠금을 유지할 수 있다.
JVM의 구성
Class Loader(클래스 로더)
JVM내로 클래스(.class file)를 로드하고, 링크를 통해 배치하는 작업을 수행하는 모듈이다. Runtime시에 동적으로 클래스를 로드한다. jar파일 내 저장된 클래스들을 JVM위에 탑재하고 사용하지 않는 메모리에서 삭제한다. (컴파일러 역할)
JAVA는 런타임을 참고한다. 즉, 클래스를 처음으로 참조할 때, 해당 클래스를 로드하고 링크하는 것이다. 그 역할에서 클래스 로더가 사용된다.
Execution Engine(실행 엔진)
클래스를 실행시키는 역할을 수행한다. Class Loader가 JVM안으로 컴파일러에 의해 변환되어 바이트 코드를 배치시키고 이 파일을 Execution Engine이 수행시키는 역할을 진행한다.
JAVA compiler
JAVA를 가지고 작성한 JAVA 코드를 JVM이 이해할 수 있는 JAVA Byte Code로 변환한다. JAVA 컴파일러는 JAVA를 설치하면 javac.exe라는 실행 파일 형태로 설치된다.
JAVA Byte Code
JVM이 이해할 수 언어로 변환된 JAVA 소스 코드를 의미한다. JAVA 컴파일러에 의해 변환되는 코드의 명령어 크기가 1바이트라서 JAVA Byte Code라고 불리고 있다. 이러한 JAVA Byte Code의 확장자는 .class 이다.
JAVA Byte Code는 JVM만 설치되어 있으면, 어떤 운영체제에서라도 실행될 수 있다.
또한 JABA Byte Code는 기계가 바로 수행할 수 있는 언어보다 비교적 인간이 보기 편한 형태로 기술된 것이다.
Execution Engine(실행 엔진)에서 Byte Code를 해석하는 두 가지 방법
Interpreter(인터프리터)
실행 엔진은 JAVA Byte Code를 명령어 단위로 읽어서 실행한다. 하나하나의 실행은 빠르나, 전체적인 실행 속도가 느리다는 단점을 가진다.
JIT(Just - In - Time)
인터프리터의 단점을 보완하기 위해 도입된 JIT 컴파일러이다.
- 인터프리터 방식으로 실행하다가 적절한 시점에 Byte Code 전체를 컴파일하여 바이너리 코드로 변경한다.
- 이후, 추가적으로 인터프리터 방식으로 실행시키지 않고 변경한 바이너리 코드를 이용하여 실행시키는 방식이다.
(Native Code) 네이티브 코드
네이티브 코드는 CPU와 운영체제(OS)가 직접 실행할 수 있는 코드를 말한다.
캐시에 보관하기 때문에 한 번 컴파일된 코드는 빠르게 수행할 수 있는 장점이 있다.
하나씩 인터프리팅하여 실행하는 것이 아니라 바이트 코드 전체가 컴파일된 바이너리 코드를 실행하는 것이기 때문에 전체적인 실행 속도는 인터 프리팅 방식보다 빠르다. JIT 컴파일러는 Byte Code를 읽어들여 빠른 속도로 기계어를 생성할 수 있다. 이런 기계어 반환은 코드가 실행되는 과정에 실시간으로 일어나며(그래서 Just-In-Time), 전체 코드의 필요한 부분만 변환한다. 기계어로 변환된 코드는 캐시에 저장되기 때문에 재사용시 컴파일을 다시 할 필요가 없다.
참고
'CS > JAVA' 카테고리의 다른 글
Call by value와 Call by reference, 비슷한 이름이지만 전혀 다른 두 친구 (0) | 2021.03.20 |
---|---|
JAVA Compile 과정, 이 일대기를 들어보자 (0) | 2021.03.20 |
HashMap, 이 친구의 이야기 공식문서로 알아보자 [Java 11] (0) | 2021.03.09 |
오버로딩(Overloading)과 오버라이딩(Overriding) (0) | 2021.03.07 |
Java의 입출력 (0) | 2021.02.21 |