✔️ JVM(Java Virtual Machine)
자바 가상 머신 JVM(Java Virtual Machine)은 자바로 애플리케이션을 가동하기 위한 또 다른 서브 프로그램이며, 자바 프로그램 실행환경을 만들어 주는 소프트웨어입니다.
자바 코드를 컴파일하여 CALSS 바이트 코드로 만들면 이 CLASS 파일을 메모리로 적재하는 것이 주요 기능입니다.
OS(Operating System)가 무엇이냐에 따라 OS별로 다른 컴파일을 해주었던 불편함을 개선하여 컴퓨터 운영체제에 맞는 자바 실행환경 JRE가 설치되어 있다면 한 번의 컴파일로 모든 OS에 적용 가능하다는 장점이 있습니다.
또한 메모리 해제의 번거로움을 GC(Garbage Collector)에 의해 자동으로 해제하여 좀 더 수월하게 개발에 집중할 수 있습니다.
📌 JVM의 특징
1. 스택 기반의 가상 머신
대표적인 컴퓨터 아키텍처인 인텔 x86 아키텍처나 ARM 아키텍처와 같은 하드웨어가 레지스터 기반으로 동작하는데 비해 JVM은 스택 기반으로 동작합니다.
2. 심볼릭 레퍼런스
기본 자료형(primitive data type)을 제외한 모든 타입(클래스와 인터페이스)을 명시적인 메모리 주소 기반의 레퍼런스가 아니라 심볼릭 레퍼런스를 통해 참조합니다.
3. 가비지 컬렉션(garbage collection)
클래스 인스턴스는 사용자 코드에 의해 명시적으로 생성되고 가비지 컬렉션에 의해 자동으로 파괴됩니다.
4. 기본 자료형을 명확하게 정의하여 플랫폼 독립성 보장
C/C++ 등의 전통적인 언어는 플랫폼에 따라 int 형의 크기가 변환합니다.
하지만 JVM은 기본 자료형을 명확하게 정의하여 호환성을 유지하고 플랫폼 독립성을 보장합니다.
5. 네트워크 바이트 오더(network byte order)
자바 클래스 파일은 네트워크 바이트 오더를 사용합니다.
인텔 x86 아키텍처가 사용하는 리틀 엔디안이나 RISC 계열 아키텍처가 주로 사용하는 빅 엔디안 사이에서 플랫폼 독립성을 유지하려면 고정된 바이트 오더를 유지해야 하므로 네트워크 전송 시에 사용하는 바이트 오더인 네트워크 바이트 오더(빅 엔디안)를 사용합니다.
📌 C와 JAVA 비교
C언어로 작성된 Test.c의 경우 컴파일 플랫폼과 타겟 플랫폼이 다를 경우에는 프로그램이 동작하지 않습니다.
윈도우에서 컴파일된 Text.exe를 리눅스 운영체제에서 실행하려면 리눅스 환경을 타겟으로 크로스 컴파일을 해야합니다.
Java로 작성된 Test.java의 경우 컴파일하면 Test.class 파일이 생성되고 이 바이트 코드는 각자의 플랫폼에 설치되어 있는 자바 가상 머신(JVM)이 운영체제에 맞는 실행 파일로 바꿔줍니다.
즉 하나의 바이트 코드로 JVM이 설치되어 있는 모든 플랫폼에서 동작이 가능합니다.
✔️ JVM의 구조
1. 자바로 개발된 프로그램을 실행하면 JVM은 OS로부터 메모리를 할당합니다.
2. 자바 컴파일러(javac)가 자바 소스코드(.java)를 자바 바이트 코드(.class)로 컴파일합니다.
3. Class Loader를 통해 JVM Runtime Data Area로 로딩합니다.
4. 로딩된 .class들은 Execution Engine을 통해 해석합니다.
5. 해석된 바이트 코드는 Runtime Data Area의 각 영역에 배치되어 수행하며 이 과정에서 Execution Engine에 의해 GC의 작동과 스레드 동기화가 이루어집니다.
📌 클래스 로더(Class Loader)
자바는 동적 로드, 즉 컴파일타임이 아니라 런타임에 클래스를 처음으로 참조할 때 해당 클래스를 로드하고 링크합니다.
이 동적 로드를 담당하는 부분이 JVM의 클래스 로더입니다.
자바 클래스 로더의 특징은 다음과 같습니다.
1. 계층 구조
클래스 로더끼리 부모-자식 관계를 이루어 계층 구조로 생성됩니다.
취상위 클래스 로더는 부트스트랩 클래스 로더(Bootstrap Class Loader)입니다.
2. 위임 모델
계층 구조를 바탕으로 클래스 로더끼리 로드를 위임하는 구조로 동작합니다.
클래스를 로드할 때 먼저 상위 클래스 로더를 확인하여 상위 클래스 로더에 있다면 해당 클래스를 사용하고,
없다면 로드를 요청받은 클래스 로더가 클래스를 로드합니다.
3. 가시성 제한
하위 클래스 로더는 상위 클래스 로더의 클래스를 찾을 수 있지만, 상위 클래스 로더는 하위 클래스 로더의 클래스를 찾을 수 없습니다.
4. 언로드 불가
클래스 로더는 클래스를 로드할 수 있지만 언로드는 할 수 없습니다.
언로드 대신 현재 클래스 로더를 삭제하고 아예 새로운 클래스 로더를 생성하는 방법을 사용할 수 있습니다.
클래스 로더가 아직 로드되지 않은 클래스를 찾으면 다음 과정을 거쳐 클래스를 로드하고 링크하고 초기화합니다.
1. 로드(Loading)
클래스를 파일에서 가져와서 JVM의 메모리에 로드합니다.
2. 검증(Verifying)
읽어 들인 클래스가 자바 언어 명세(Java Language Specification) 및 JVM 명세에 명시된 대로 잘 구성되어 있는지 검사합니다. 클래스 로드의 전 과정 중에서 가장 까다로운 검사를 수행하는 과정으로서 가장 복잡하고 시간이 많이 걸립니다.
3. 준비(Preparing)
클래스가 필요로 하는 메모리를 할당하고 클래스에서 정의된 필드, 메서드, 인터페이스들을 나타내는 데이터 구조를 준비합니다.
4. 분석(Resolving)
클래스의 상수 풀 내 모든 심볼릭 레퍼런스를 다이렉트 레퍼런스로 변경합니다.
5. 초기화
클래스 변수들을 적절한 값으로 초기화합니다.
즉 static initializer들을 수행하고 static 필드들을 설정된 값으로 초기화합니다.
📌 실행 엔진(Execution Engine)
클래스 로더를 통해 JVM 내의 런타임 데이터 영역에 배치된 바이트 코드는 실행 엔진에 의해 실행됩니다.
실행 엔진은 자바 바이트 코드를 명령어 단위로 읽어서 실행하는데 CPU가 기계 명령어를 하나씩 실행하는 것과 비슷합니다. 바이트 코드의 각 명령어는 1바이트짜리 OpCode와 추가 피연산자로 이루어져 있으며, 실행 엔진은 하나의 OpCode를 가져와서 피연산자와 함께 작업을 수행하고 다음 OpCode를 수행하는 식으로 동작합니다.
그런데 자바 바이트 코드는 기계가 바로 수행할 수 있는 언어보다는 비교적 인간이 보기 편한 형태로 기술된 것입니다.
그래서 실행 엔진은 이와 같은 바이트 코드를 실제로 JVM 내부에서 기계가 실행할 수 있는 형태로 변경하며 그 방식은 다음 두 가지가 있습니다.
1. 인터프리터
바이트 코드 명령어를 하나씩 읽어서 해석하고 실행합니다. 즉, 바이트 코드라는 '언어'는 기본적으로 인터프리터 방식으로 동작합니다.
하나씩 해석하고 실행하기 때문에 바이트 코드 각각의 해석은 빠른 대신 인터프리팅 결과의 실행은 느리다는 흔히 얘기하는 인터프리터 언어의 단점을 그대로 갖고 있습니다.
2. JIT(Just-In-Time) 컴파일러
인터프리터의 단점을 보완하기 위해 도입된 것이 JIT 컴파일러입니다.
인터프리터 방식으로 실행하다가 적절한 시점에서 바이트 코드 전체를 컴파일하여 네이티브 코드로 변경하고 이후에는 해당 메서드를 더 이상 인터프리팅하지 않고 네이티브 코드로 직접 실행하는 방식입니다.
네이티브 코드를 실행하는 것이 하나씩 인터프리팅하는 것보다 빠르고 네이티브 코드는 캐시에 보관하기 때문에 한 번 컴파일된 코드는 계속 빠르게 수행됩니다.
📌 가비지 컬렉터
가비지 컬렉션(Garbage Collection)
✔️ Garbage Collection이란? GC(Garbage Collection)는 자바의 메모리 관리 방법 중의 하나로 JVM의 Heap 영역에서 동적으로 할당했던 메모리 영역 중 필요 없게 된 메모리 영역을 주기적으로 삭제하는 프로
0chn-xxx.tistory.com
📄 참고
'📚 Study > JAVA' 카테고리의 다른 글
[JAVA] 객체지향개념1 (0) | 2022.04.30 |
---|---|
[JAVA] 변수, 타입, 형변환, 연산, 조건문&반복문, 배열 (0) | 2022.04.29 |
[JAVA] 자바의 메모리 구조 (0) | 2022.04.27 |
[JAVA] 가비지 컬렉션(Garbage Collection) (0) | 2022.04.27 |
[JAVA] JVM? JRE? JDK? (0) | 2022.04.27 |