Java Virtual Machine
1. JAVA의 중요 특징과 JVM
JAVA는 어떤 하드웨어, 운영체제에 상관없이 컴파일된 코드가 플랫폼 독립적이라는 점이 가장 큰 특징이다. 다시말해서 어느 플랫폼이든 작성한 소스를 변경할 필요없이 다 실행시킬 수 있다는 것이다.
위의 특징을 가능하게 만드는 것이 JVM(JAVA Virtual Machine)이다.
JVM은 크게 2가지의 기능을 갖고있다고 말할 수 있다.
- 자바 프로그램이 어느 기기나 운영체제 상에서도 실행될 수 있도록 하는 것
- 프로그램 메모리를 관리하고 최적화하는 것
아래로 가기 전 JAVA로 작성한 코드가 수행되는 과정을 아래 링크에서 보고 오는것을 추천한다.
2. JVM의 특징
- 스택 기반의 가상 머신
- 대표적인 컴퓨터 아키텍처인 인텔 x86 아키텍처나 ARM 아키텍처와 같은 하드웨어가 레지스터 기반으로 동작하는 데 비해 JVM은 스택 기반으로 동작한다.
- 심볼릭 레퍼런스
- 기본 자료형(primitive data type)을 제외한 모든 타입(클래스와 인터페이스)을 명시적인 메모리 주소 기반의 레퍼런스가 아니라 심볼릭 레퍼런스를 통해 참조한다.
- 가비지 컬렉션(garbage collection)
- C/C++ 등의 전통적인 언어는 프로그래머가 모든 프로그램 메모리를 관리했었다.
- JVM은 클래스 인스턴스는 사용자 코드에 의해 명시적으로 생성되고 가비지 컬렉션에 의해 자동으로 파괴된다.
- 기본 자료형을 명확하게 정의하여 플랫폼 독립성 보장
- 전통적인 언어는 플랫폼에 따라 int 형의 크기가 변한다.
- JVM은 기본 자료형을 명확하게 정의하여 호환성을 유지하고 플랫폼 독립성을 보장한다.
- 네트워크 바이트 오더(network byte order)
- 자바 클래스 파일은 네트워크 바이트 오더(빅 엔디안)를 사용한다. 인텔 x86 아키텍처가 사용하는 리틀 엔디안이나, RISC 계열 아키텍처가 주로 사용하는 빅 엔디안 사이에서 플랫폼 독립성을 유지하려면 고정된 바이트 오더를 유지해야하므로 네트워크 전송 시에 사용하는 바이트 오더인 네트워크 바이트 오더를 사용한다.
3. JVM의 구조
구조는 크게 3가지 부분으로 볼 수 있다.
- 클래스로더
- 런타임 데이터 영역(Runtime Data Areas)
- 실행엔진
3.1. 클래스로더
자바는 컴파일타임이 아닌 런타임에 클래스를 처음으로 참조할때 해당 클래스를 로드하고 링크하는 특징이 있다. 이 것을 동적 로드라고 한다. 이 동적로드를 담당하는 부분이 JVM의 클래스 로더이다.
3.1.1. 클래스로더 특징
- 계층구조 : 여러 클래스 끼리 계층구조로 되어있다.
- 위임 모델 : 계층 구조를 바탕으로 클래스 로더끼리 로드를 위임하는 구조로 동작한다. 클래스를 로드할 때 먼저 상위 클래스 로더를 확인하여 상위 클래스 로더에 있다면 해당 클래스를 사용하고, 없다면 로드를 요청받은 클래스 로더가 클래스를 로드한다.
- 가시성(visibility) 제한 : 하위 클래스 로더는 상위 클래스 로더의 클래스를 찾을 수 있지만, 상위 클래스 로더는 하위 클래스 로더의 클래스를 찾을 수 없다.
- 언로드 불가 : 클래스 로더는 클래스를 로드할 수는 있지만 언로드할 수는 없다. 언로드 대신, 현재 클래스 로더를 삭제하고 아예 새로운 클래스 로더를 생성하는 방법을 사용할 수 있다.
각 클래스 로더는 로드된 클래스들을 보관하는 네임스페이스(namespace)를 갖는다. 클래스를 로드할 때 이미 로드된 클래스인지 확인하기 위해서 네임스페이스에 보관된 FQCN(Fully Qualified Class Name)을 기준으로 클래스를 찾는다. 비록 FQCN이 같더라도 네임스페이스가 다르면, 즉 다른 클래스 로더가 로드한 클래스이면 다른 클래스로 간주된다.
3.2. 런타임 데이터 영역
JVM이 운영체제 위에서 실행되면서 할당받는 메모리 영역이다.
런타임 데이터 영역은 총 5가지 영역으로 나누어진다.
PC 레지스터, JVM 스택, 네이티브 메서드 스택, 힙, 메서드 영역 (이 중에 힙과 메서드 영역은 모든 스레드가 공유해서 사용함)
- PC 레지스터 : 스레드가 어떤 명령어로 실행되어야 할지 기록하는 부분(JVM 명령의 주소를 가짐)
- 스택 : 지역변수, 매개변수, 메서드 정보, 임시 데이터 등을 저장
- 네이티브 메서드 스택 : 실제 실행할 수 있는 기계어로 작성된 프로그램을 실행시키는 영역
- 힙 : 런타임에 동적으로 할당되는 데이터가 저장되는 영역. 객체나 배열 생성이 여기에 해당함(또한 힙에 할당된 데이터들은 가비지컬렉터의 대상이 됨. JVM 성능 이슈에서 가장 많이 언급되는 공간임)
- 메서드 영역 : JVM이 시작될 때 생성되고, JVM이 읽은 각각의 클래스와 인터페이스에 대한 런타임 상수 풀, 필드 및 메서드 코드, 정적 변수, 메서드의 바이트 코드 등을 보관함
3.3. 실행 엔진
실행 엔진은 클래스 로더를 통해 런타임 데이터 영역에 배치된 바이트 코드를 명령어 단위로 읽어서 실행한다.
이 과정에서 실행 엔진은 바이트코드를 아래의 두가지 방식으로 변경한다.
- 인터프리터
- 바이트 코드를 하나씩 읽어서 실행
- 한줄 한줄 실행은 빠르나 전체적인 실행 속도는 느리다.
- JIT 컴파일러 (Just-In-Time Compiler)
- 인터프리터의 단점을 보완하기 위해 도입된 방식
- 바이트 코드 전체를 컴파일하여 바이너리 코드로 변경 후 해당 메서드를 더이상 인터프리팅 하지 않고, 바이너리 코드로 직접 실행하는 방식
- 전체적인 실행속도는 인터프리팅보다 빠르다.
참고자료
- Tech Interview(gyoogle.dev)
- dalpang.e 티스토리
- 알짜배기 프로그래머 티스토리
- 마이구미의 HelloWorld 티스토리
- swiftymind 티스토리
- NAVER D2 Hello world
댓글 남기기