[JAVA]JVM은 무엇이며 자바 코드는 어떻게 실행하는 것인가

홍준표·2022년 5월 27일
0

java 스터디

목록 보기
1/8

목표

자바 소스파일(.java)을 JVM으로 실행하는 과정 이해하기


학습할 것

  • JVM이란 무엇인가
  • 컴파일 하는 방법
  • 실행하는 방법
  • 바이트코드란 무엇인가
  • JIT 컴파일러란 무엇이며 어떻게 동작하는지
  • JVM 구성요소
  • JDK와 JRE의 차이

JVM이란 무엇인가

JVM

  1. Java Virtual Machine 은 자바를 실행하기 위한 자바 가상머신이다. 자바와 운영체제 사이에서 중계자 역할을 하며, 자바가 운영체제 종류에 영향을 받지 않고 실행 가능하도록 한다.

  2. 운영체제 위에서 동작하는 프로세스로 자바 코드를 컴파일 해서 얻은 바이트 코드를 해당 운영체제가 이해할 수 있는 기계어로 바꿔 실행시켜주는 역할을 한다.

  3. GC(Garbage Collection)을 이용하여 자동으로 메모리 관리를 해준다.

자바 소스파일(.java)을 JVM으로 실행 과정

  1. 프로그램이 실행되면 JVM은 운영체제로부터 이 프로그램이 필요로 하는 메모리를 할당받음
    JVM은 이 메모리를 용도에 따라 여러 영역으로 나누어 관리

  2. 자바 컴파일러(javac)가 자바 소스코드(.java)를 읽어들여 자바 바이트코드(.class)로 변환
    .java -> .class

  3. Class Loader를 통해 class 파일을 JVM 메모리에 적재

  4. JVM 메모리 영역에 적재된 class 파일을 Execution engine을 통해 해석

자바 컴파일 및 실행 방법

자바 소스를 컴파일 및 실행하기 위해서는 기본적으로 아래의 두 프로그램이 필요하다.

  • javac.exe
  • java.exe

javac.exe는 자바 소스코드를 컴파일 할 때 사용하는 프로그램이며 컴파일된 바이트코드를 실행할 때 java.exe 사용한다.

javac.exe는 JDK, java.exe는 JRE에 포함되어 있기에 JDK 과 JRE를 설치해야 하지만 과거와 다르게 요즘은 JDK에 JRE가 포함된 형태로 배포되고 있기에 JDK만 설치해도 무관하다.

컴파일 하는 방법

  1. 소스코드.java 작성
  2. javac.exe 즉, 자바 컴파일러를 통해 자바 소스코드를 바이트 코드로 컴파일한다.(.class파일 생성)

컴파일이 정상적으로 완료되면 해당 경로에 소스파일명.class 생성

실행하는 방법

  1. java.exe 즉, 자바 인터프리터를 통해 바이트 코드를 실행한다.
  2. .class 파일 실행

    내부적인 진행순서
    a. 프로그램의 실행에 필요한 클래스(.class파일)을 로드
    b. .class파일 검사 (파일형식, 악성코드 체크)
    c. 지정된 클래스에서 main(String[ ] args) 호출

하나의 자바 어플리케이션에는 main메소드를 포함한 클래스가 반드시 존재해야 한다.

바이트코드란 무엇인가

특정 하드웨어가 아닌 가상 컴퓨터(Virtual Machine)에서 돌아가는 실행 프로그램을 위한 이진 표현법으로 하드웨어가 아닌 소프트웨어에 의해 처리되기에 기계어보다 추상적이다.

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

자바 바이트 코드는 기계가 바로 수행할 수 있는 언어보다는 비교적 인간이 보기 편한 형태로 기술된 것으로 실행 엔진은 이와 같은 바이트코드를 JVM내부에서 기계가 실행할 수 있는 형태로 병경하는데 이때 인터프리터,JIT 컴파일러를 사용한다.

JIT 컴파일러란?

JIT(Just-In-Time) 컴파일러는 인터프리터의 단점을 보완하기 위해 도입되었다. 인터프리터 방식으로 실행하다 적절한 시점에 바이트코드 전체를 네이티브 코드로 바꾼다. 그 다음부터 인터프리터는 네이티브 코드로 컴파일된 코드를 바로 사용한다. 네이티브 코드를 실행하는 것이 하나씩 인터프리팅 하는 것보다 빠르고, 네이티브 코드는 캐시에 보관하기 때문에 한 번 컴파일된 코드는 계속 빠르게 수행된다.

JIT컴파일러가 컴파일 하는 과정은 바이트코드를 하나씩 인터프리팅하는 것보다 훨씬 오래걸리기에 만약, 한번만 실행되는 코드라면 컴파일 하지 않고 인터프리팅하는게 유리하다. 따라서 컴파일러를 사용하는 JVM들은 내부에서 해당 메소드의 수행빈도를 확인 후 일정 정도를 넘을 때 컴파일을 수행한다.

JIT 컴파일러의 동작 형태

JIT 컴파일러는 바이트코드를 우선 중간 단계의 표현인 IR(Intermediate Representation)로 변환하여 최적화를 수행하고 그 다음 네이티브 코드를 생성한다.

인터프리터 : 바이트코드 명령어를 하나씩 읽어서 해석하고 실행한다.

Intermediate Representation : 소스 코드를 표현하기 위해 컴파일러 또는 가상 시스템에서 내부적으로 사용하는 데이터 구조 또는 코드로 최적화와 번역 등 추가처리를 위해 도움이 되도록 설계되었다.

JVM 구성 요소


클래스 로더(Class Loder)가 컴파일된 자바 바이트 코드를 메모리영역에 로드하고, 실행 엔진(Execution Engine)이 자바 바이트코드를 실행한다.

클래스 로더 시스템

자바는 런타임에 클래스를 처음으로 참조할 때 해당 클래스를 로드하고 링크하는 특징이 있다. 이 동적 로드를 담당하는 부분이 JVM의 클래스 로더이다.

클래스 로더의 특징

  • 계층 구조: 클래스 로더끼리 부모-자식 관계를 이뤄 계층 구조로 생성된다. 여기서 최상위 클래스 로더는 부트스트랩 클래스 로더(Bootstrap Class Loader)이다.

  • 위임 모델: 계층 구조를 바탕으로 클래스 로더끼리 로드를 위임하는 구조로 동작한다. 클래스를 로드할 때 먼저 상위 클래스 로더를 확인하여 상위 클래스 로더에 있다면 해당 클래스를 사용하고 없다면 로드를 요청받은 클래스로더가 클래스를 로드한다.

  • 가시성(visiblility) 제한: 하위 클래스 로더는 상위 클래스 로더를 찾을 수 있지만 상위 클래스 로더는 하위 클래스로더의 클래스를 찾을 수 없다.

  • 언로드 불가: 클래스 로더는 클래스를 로드할 수는 있지만 언로드할 수는 없다. 언로드 대신, 현재 클래스 로더를 새로 삭제하고 아예 새로운 클래스 로더를 생성하는 방법을 사용할 수 있다

클래스 로더의 역할
클래스를 JVM의 메모리에 로드한다. 런타임 데이터 영역에 바이트코드로 배치한다.

읽어 들인 클래스를 검사한다.

클래스에 필요한 메모리를 할당하고, 클래스에서 정의된 필드, 메소드, 인터페이스들을 나타내는 데이터 구조를 준비한다.

클래스의 상수 풀 내 모든 심볼릭 레퍼런스를 다이렉트 레퍼런스로 변경한다.

메모리

JVM의 메모리 영역으로 자바 애플리케이션을 실행할 때 사용되는 데이터들을 적재하는 영역.

  • 스택 영역: 지역 변수 ,파라미터, 리턴 값, 연산에 사용되는 임시 값등이 생성되는 영역으로 클래스 수준의 정보를 저장하고 공유자원이다.
int a = 10;

위와 같은 코드를 작성했다면, a라는 메모리 영역을 잡고 해당 영역에 10을 할당한다. 즉, 스택에 a라는 이름의 값이 10인 메모리 공간을 만든다.

  • PC Register: 쓰레드가 생성될 때마다 생성되는 영역으로 현재 쓰레드가 실행되는 부분의 주소와 명령을 저장하고 있는 영역이다. 이것을 이용해 쓰레드를 돌아가며 수행할 수 있게 한다.

  • 네이티브 메소드 스택: 자바 외 언어로 작성된 네이티브 코드를 위한 메모리영역으로 보통 C/C++ 등의 코드를 수행하기 위한 스택이다.

  • 힙: new 키워드로 생성된 객체와 배열이 생성되는 영역이다. 메소드 영역에 로드된 클래스만 생성이 가능하고 Carbage Collector가 참조되지 않는 메모리를 확인/제거하는 영역이다.

위에서 설명했듯이 클래스 수준의 정보는 스택에 저장되지만 객체의 인스턴스는 힙영역에 생성된다. 그리고 스택영역에는 힙영역의 주소값을 저장한다.

  • 모든 스레드가 공유하는 영역으로 JVM이 시작될 때 생성된다. 저장하는 내용은 JVM이 읽어들인
    • 각각의 클래스

    • 인터페이스에 대한 런타임 상수 풀

    • 필드와 메소드 정보

    • static 변수

    • 매소드의 바이트코드

      등을 저장한다.

실행 엔진(Execution Engine)

클래스 로더에 의해 메모리에 적재된 클래스(Bytecodes)들을 기계어로 변경해 명령어 단위로 실행하는 역할을 한다.
명령어를 하나 하나 실행하는 방식은 다음과 같이 두 가지가 있다.

  1. 인터프리터(InterPreter): 바이트코드 명령어를 하나씩 읽어서 해석하고 실행한다.
  2. JIT(Just-In-Time) 컴파일러: 인터프리터 방식에 캐싱 방식을 더해 빠르게 수행한다.

그리고 더 이상 참조되지 않는 객체를 모아서 정리하는 GC(Garbage Collector)가 있다. GC는 상황에 맞는 적절한 GC를 사용한다.

JDK와 JRE의 차이

JRE(Java Runtime Enviroment): JVM + 라이브러리

  • 자바 애플리케이션을 실행할 수 있도록 구성된 배포판.
  • JVM과 핵심 라이브러리 및 자바 런타임 환경에서 사용하는 프로퍼티 세팅이나 리소스 파일을 가지고 있다.
  • 개발 관련 도구는 포함하지 않는다.
    JDK 11 이후(Java Module 추가)로는 Jlink가 추가되어 JRE가 따로 배포되지 않는다.

JDK(Java Development Kit): JRE + 개발 툴

  • JRE + 개발에 필요한 툴
  • 소스 코드 작성할 떼 사용하는 자바 언어는 플랫폼에 독립적
  • 오라클은 자바 11부터는 JDK만 제공하며 JRE를 따로 제공하지 않는다.
profile
프로그래밍 시작

0개의 댓글