Java Virtual Machine

1. JAVA의 중요 특징과 JVM

JAVA Program

JAVA는 어떤 하드웨어, 운영체제에 상관없이 컴파일된 코드가 플랫폼 독립적이라는 점이 가장 큰 특징이다. 다시말해서 어느 플랫폼이든 작성한 소스를 변경할 필요없이 다 실행시킬 수 있다는 것이다.

위의 특징을 가능하게 만드는 것이 JVM(JAVA Virtual Machine)이다.

JVM은 크게 2가지의 기능을 갖고있다고 말할 수 있다.

  1. 자바 프로그램이 어느 기기나 운영체제 상에서도 실행될 수 있도록 하는 것
  2. 프로그램 메모리를 관리하고 최적화하는 것

아래로 가기 전 JAVA로 작성한 코드가 수행되는 과정을 아래 링크에서 보고 오는것을 추천한다.

자바 컴파일 과정 포스트로 가기

2. JVM의 특징

  • 스택 기반의 가상 머신
    • 대표적인 컴퓨터 아키텍처인 인텔 x86 아키텍처나 ARM 아키텍처와 같은 하드웨어가 레지스터 기반으로 동작하는 데 비해 JVM은 스택 기반으로 동작한다.
  • 심볼릭 레퍼런스
    • 기본 자료형(primitive data type)을 제외한 모든 타입(클래스와 인터페이스)을 명시적인 메모리 주소 기반의 레퍼런스가 아니라 심볼릭 레퍼런스를 통해 참조한다.
  • 가비지 컬렉션(garbage collection)
    • C/C++ 등의 전통적인 언어는 프로그래머가 모든 프로그램 메모리를 관리했었다.
    • JVM은 클래스 인스턴스는 사용자 코드에 의해 명시적으로 생성되고 가비지 컬렉션에 의해 자동으로 파괴된다.
  • 기본 자료형을 명확하게 정의하여 플랫폼 독립성 보장
    • 전통적인 언어는 플랫폼에 따라 int 형의 크기가 변한다.
    • JVM은 기본 자료형을 명확하게 정의하여 호환성을 유지하고 플랫폼 독립성을 보장한다.
  • 네트워크 바이트 오더(network byte order)
    • 자바 클래스 파일은 네트워크 바이트 오더(빅 엔디안)를 사용한다. 인텔 x86 아키텍처가 사용하는 리틀 엔디안이나, RISC 계열 아키텍처가 주로 사용하는 빅 엔디안 사이에서 플랫폼 독립성을 유지하려면 고정된 바이트 오더를 유지해야하므로 네트워크 전송 시에 사용하는 바이트 오더인 네트워크 바이트 오더를 사용한다.

3. JVM의 구조

JAVA Program

구조는 크게 3가지 부분으로 볼 수 있다.

  1. 클래스로더
  2. 런타임 데이터 영역(Runtime Data Areas)
  3. 실행엔진

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)
    • 인터프리터의 단점을 보완하기 위해 도입된 방식
    • 바이트 코드 전체를 컴파일하여 바이너리 코드로 변경 후 해당 메서드를 더이상 인터프리팅 하지 않고, 바이너리 코드로 직접 실행하는 방식
    • 전체적인 실행속도는 인터프리팅보다 빠르다.

참고자료

댓글 남기기