목표
  • 자바 소스 파일(.java)을 JVM으로 실행하는 과정 이해하기.
학습할 것
  • JVM이란 무엇인가
  • 컴파일 하는 방법
  • 실행하는 방법
  • 바이트코드란 무엇인가
  • JIT 컴파일러란 무엇이며 어떻게 동작하는지
  • JVM 구성 요소
  • JDK와 JRE의 차이

JVM이란 무엇인가

JVM의 용도와 정의

  • Java로 개발한 프로그램을 컴파일하여 만들어지는 바이트코드를 실행시키기 위한 가상머신입니다.
  • 플랫폼 독립적으로, JVM이 실행 가능한 환경이라면 어디서든 Java 프로그램이 실행될 수 있도록 합니다. 즉, Java의 모토인 Write once, Run anywhere는 JVM을 통해 가능한 것입니다.
  • 프로그램 메모리를 관리하고 최적화합니다.

바이트코드는 실제의 기계에서 직접 실행되는 것이 아니라 JVM의 해석 단계를 거쳐 실행되므로 Java로 개발된 프로그램은 같은 기능의 네이티브 언어보다 실행 속도가 느립니다. 과거에는 바이트코드를 순수하게 인터프리트하여 매우 느렸으나 현재는 JIT 컴파일의 도입과 하드웨어의 발전으로 성능이 개선되었습니다.

JVM은 추상적인 머신이며, 메모리의 접근을 가상 머신 차원에서 관리하고 있으므로 런타임에 최적화가 가능합니다. 매우 극단적이고 특수한 상황을 가정하면 극히 일부 기능에 대해서는 네이티브 언어보다 더 우월한 성능을 보여 주기도 합니다. 하지만 JIT 컴파일 시간, 가비지 컬렉션을 위한 시간 등이 필요하므로 근본적인 한계가 있습니다.

컴파일 & 실행하는 방법

우리가 Java Source Code를 작성하면 java라는 확장자를 가진 파일로 만들어지게 됩니다. Java Source파일은 단순히 코드만을 담고 있는 파일일 뿐, 이 자체로는 수행 불가능하므로 수행하도록 하기 위해서는 Class 파일의 형태로 변경해 주어야 합니다. 이 작업을 Compile이라고 합니다.

Java에서 Compile은 보통 JDK에 내장되어 있는 'javac'라고 하는 Compiler를 이용하여 수행합니다. 이 작업을 수행하면 Source File과 같은 이름이지만 class라는 확장자를 가지는 Binary파일이 생성됩니다.

bytecode라고도 부르는 이 class라는 확장자를 가진 파일은 수행이 가능한 형태의 파일입니다. exe 파일처럼 수행할수 있는 형태가 아니라 단순히 수행 가능한 형태라는 말입니다. 이것을 수행하기위해서는 적어도 JRE(Java Runtime Environment)라는 것이 필요합니다. 물론 JDK도 가능합니다.

이 프로그램을 수행하기 위해서는 'java'라는 프로그램을 호출하여 우리가 생성한 class파일을 인수로 제공하면 됩니다. 이때 'java'라고 하는 프로그램은 Java실행 환경에 class파일을 가지고 들어가는 역할을 하게 됩니다. 'java'라고 하는 프로그램은 JVM을 하나의 프로세스로 올리는 작업과 함께 class파일의 로딩도 수행합니다.

그 이후 class파일을 분석하여 JRE내에 있는 Java Application Program Interface(Java API)와 더불어 프로그램을 수행하도록 합니다. 그 과정은 아래와 같습니다.

  • bytecode는 JVM 내부에 있는 Class Loader을 통해 Execution Engine 으로 배치됩니다.
  • Execution Engine 안은 Interpreter와 JIT로 구성되어 있는데, Interpreter은 코드를 한줄씩 읽어들이다가 일정 시점이 되면 JIT Compiler에 캐싱하여 보관합니다.
  • 한번 컴파일된(JIT에 캐싱된)코드는 빠르게 실행되는 장점이 있지만, 한번만 실행될 코드는 Interpreter 하는게 더 유리할 수도 있습니다. (컴파일 비용이 들기 때문)
  • Execution Engine에서 실행한 필요한 녀석들은 Runtime Area로 이동하며, JVM의 메모리에 올려 실행합니다.

바이트코드란 무엇인가

자바에서 코드를 컴파일하면 바이트코드 즉 (.class)형태로 출력이 되는데 이 Class형태는 JVM에 의해 런타임시완벽한 기계코드로 변경되어 실행됩니다. 자바 바이트 코드는 자바 가상 머신만 설치되어 있으면, 어떤 운영체제에서라도 실행될 수 있습니다.

하지만 바이트 코드를 완전한 기계코드로 변환하는 과정에서 일반적인 컴파일 언어보다 속도가 많이 느리다는 단점이 있습니다.

JIT 컴파일러란 무엇이며 어떻게 동작하는지

명령어를 하나 하나 실행하는 Interpreter 방식이 있고 JIT(Just-In-Time) 컴파일러를 이용하는 방식이 있습니다.

Interpreter는 바이트 코드를 한줄씩 읽기 때문에 실행이 느린 단점이 있었습니다. 이러한 단점을 보완하기 위해 나온 것이 JIT Compiler 입니다.

처음에는 인터프리터 방식으로 실행을 하다가 적절한 시점에 바이트 코드 전체를 컴파일해서 더이상 인터프리팅 하지 않고 해당 코드를 직접 실행 하는 것 입니다.

JIT Compiler에 의해 해석된 코드는 캐시에 보관하기 때문에 한 번 컴파일 된 후에는 빠르게 수행하는 장점이 있습니다. 하지만 인터프리팅 방식보다는 훨씬 오래 걸리므로 한번만 실행하면 되는 코드는 인터프리팅 하는 것이 유리합니다.

JVM 구성 요소

  • Class Loader: JVM내로 클래스를 로드하고 링크를 통해 배치하는 작업을 수행하는 모듈로써 런타임시 동적으로 클래스를 로드합니다.
  • Execution Engine: Class Loader를 통해 JVM 내의 런타임 데이터 영역에 배치된 바이트 코드를 실행합니다. 이 때, Execution Engine은 자바 바이트 코드를 명령어 단위로 읽어서 실행합니다.
  • Garbage Collector: JVM은 Garbage Collector를 통해 메모리 관리 기능을 자동으로 수행합니다. 애플리케이션이 생성한 객체의 생존 여부를 판단하여 더 이상 사용되지 않는 객체를 해제하는 방식으로 메모리를 자동 관리합니다.
  • Runtime Data Areas: JVM이 운영체제 위에서 실행되면서 할당받는 메모리 영역입니다. Class Loader에서 준비한 데이터들을 보관하는 저장소입니다.

  • Method (Static) Area
    • JVM이 읽어들인 클래스와 인터페이스 대한 런타임 상수 풀, 멤버 변수(필드), 클래스 변수(Static 변수), 생성자와 메소드를 저장하는 공간입니다.
  • Runtime Constant Pool
    • 메소드 영역에 포함되지만 독자적 중요성이 있습니다.
    • 클래스 파일 constant_pool 테이블에 해당하는 영역입니다.
    • 클래스와 인터페이스 상수, 메소드와 필드에 대한 모든 레퍼런스를 저장합니다.
    • VM은 런타임 상수 풀을 통해 해당 메소드나 필드의 실제 메모리 상 주소를 찾아 참조합니다.
  • Heap Area
    • JVM이 관리하는 프로그램 상에서 데이터를 저장하기 위해 런타임 시 동적으로 할당하여 사용하는 영역입니다.
    • New 연산자로 생성된 객체 또는 객체(인스턴스)와 배열을 저장합니다.
    • 힙 영역에 생성된 객체와 배열은 스택 영역의 변수나 다른 객체의 필드에서 참조합니다.
    • 참조하는 변수나 필드가 없다면 의미 없는 객체가 되어 GC의 대상이 됩니다.
  • Stack Area
    • 각 스레드마다 하나씩 존재하며, 스레드가 시작될 때 할당됩니다.
    • 메소드를 호출할 때마다 프레임(Frame)을 추가(push)하고 메소드가 종료되면 해당 프레임을 제거(pop)하는 동작을 수행합니다.
    • 선입후출(FILO, First In Last Out) 구조로 push와 pop 기능을 사용합니다.
    • 메소드 호출 시 생성되는 스레드 수행정보를 기록하는 Frame을 저장합니다.
    • 메소드 정보, 지역변수, 매개변수, 연산 중 발생하는 임시 데이터 저장합니다.
    • 기본(원시)타입 변수는 스택 영역에 직접 값을 가집니다.
    • 참조타임 변수는 힙 영역이나 메소드 영역의 객체 주소를 가집니다.
  • PC Register
    • 현재 수행 중인 JVM 명령 주소를 갖습니다.
    • 프로그램 실행은 CPU에서 인스트럭션(Instruction)을 수행합니다.
    • CPU는 인스트럭션을 수행하는 동안 필요한 정보를 CPU 내 기억장치인 레지스터에 저장합니다.
    • 연산 결과값을 메모리에 전달하기 전 저장하는 CPU 내의 기억장치입니다.
  • Native Method Stack Area
    • 자바 외 언어로 작성된 네이티브 코드를 위한 Stack입니다.
    • 즉, JNI(Java Native Interface)를 통해 호출되는 C/C++ 등의 코드를 수행하기 위한 스택입니다.
    • 네이티브 메소드의 매개변수, 지역변수 등을 바이트 코드로 저장합니다.

JDK와 JRE의 차이

JDK ( Java Development Kit )

  • Java 용 SDK ( Software Development Kit ) 입니다.
  • Java 개발자가 Java 기반 프로그램을 개발할 수 있도록 컴파일러, 툴 등을 제공합니다.
  • 다만 자바로 개발된 Application을 사용하기 위한 실행 환경은 있어야 합니다. 그 역할을 JRE 가 합니다.
    • JDK 는 다시 J2EE ( Enterprise Edition ) , J2SE ( Standard Edition ) , J2ME ( Micro Edition ) 등으로 분류합니다.
    • EE는 기업용 대규모 개발 환경에서 적합한 버전
    • SE는 일반적인 개발 환경
    • ME는 PDA나 Embeded 환경에서의 개발환경을 제공

JRE ( Java Runtime Environment )

  • Java 프로그램을 실행시키기 위한 환경을 제공합니다. 즉, Java 언어로 만들어져서 컴파일된 프로그램을 실행하려면 JRE 는 설치되어 있어야 합니다.

정리하자면,

  • JDK = JRE + 개발툴 + Java 컴파일러
  • JRE = JVM, 라이브러리, 기타 애플릿이나 어플리케이션 구동 요소
출처

+ 따끈한 최근 게시물