Java

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

donggi 2022. 7. 24. 21:19

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

JVM이란 무엇인가

JVM이란 Java Virtual Machine, 자바를 실행하기 위한 가상 컴퓨터의 약자다.

  • Java는 OS에 종속적이지 않다. 그렇기 때문에 OS에 종속 받지 않고 Java를 실행하기 위해선 OS 위에서 Java를 실행시킬 무언가가 필요하다. 이 때 필요한 게 JVM이다.
  • JVM은 OS에 종속받지 않고 CPU가 Java를 인식, 실행할 수 있게하는 가상 컴퓨터이다.
  • Java 소스코드 (.java)는 CPU가 인식하지 못하므로 기계어로 컴파일 해줘야한다. 하지만 Java는 JVM이라는 가상머신을 거쳐 OS에 도달하기 때문에 OS가 인식할 수 있는 기계어로 컴파일 되는 게 아니라 JVM이 인식할 수 있는 Java bytecode(.class)로 변환되어야 한다. Java compiler가 여기서 *.java 파일을 *.class 라는 Java bytecode로 변환한다.
  • 변환된 bytecode는 기계어가 아니기 때문에 OS에서 바로 실행되지 않는다.
  • JVM이 OS가 bytecode를 이해할 수 있도록 해석해준다. 따라서 bytecode는 JVM 위에서 OS에 종속 받지 않고 실행될 수 있다.

 

컴파일 하는 방법

  1. 개발자가 자바 소스코드(.java)를 작성한다.
  2. Java Compiler가 자바 소스파일을 컴파일한다. 이 때 나오는 파일은 자바 바이트 코드 파일(.class)로 아직 컴퓨터가 읽을 수 없고 JVM이 이해할 수 있는 코드다. 바이트 코드의 각 명령어는 1바이트 크기Opcode추가 피연산자로 이루어져있다.
  3. 컴파일된 바이트 코드를 JVM의 **클래스 로더(Class Loader)**에게 전달한다.
  4. 클래스 로더는 동적로딩을 통해 필요한 클래스들을 로딩, 링크하여 **런타임 데이터 영역(JVM의 메모리)**에 올린다.

 

실행하는 방법

  • 앞에서 컴파일된 바이트 코드를 JVM의 클래스 로더(Class Loader)에게 전달한다.
  • 클래스 로더는 동적 로딩을 통해 필요한 클래스들을 로딩, 링크하여 런타임 데이터 영역(JVM의 메모리)에 올린다.
  • 실행엔진(Execution Engine)은 JVM 메모리에 올라온 클래스 파일(바이트 코드)을 명령어 단위로 하나씩 가져와 실행한다.
    1. 인터프리터: 바이트 코드를 한 줄씩 읽으면서 OS가 실행할 수 있도록 기계어로 번역한다. 초기 JVM은 인터프리터 방식만 이용하여 한 줄 한 줄 읽기에 실행 속도가 느린 단점이 있었지만, JIT 컴파일러를 통해 속도를 보완했다.
    2. JIT 컴파일러(Just-In-Time Compiler): JIT 컴파일러는 반복되는 코드를 기계어로 변환하여 캐싱한다. 캐싱된 기계어는 인터프리터가 해석하는 게 아닌 JIT 컴파일러가 캐시에서 꺼내 바로 실행한다.

 

바이트코드란 무엇인가

  • Java Bytecode란 JVM이 이해할 수 있는 언어로 변환된 Java 소스 코드를 의미한다.
  • Java Compiler에 의해 변환되는 코드의 명령어 크기가 1바이트여서 Java Bytecode라고 불리고 있다.
  • java Bytecode의 확장자는 .class다.
  • Java Bytecode는 JVM만 설치되어 있으면, 어떤 운영체제에서라도 실행될 수 있다.

 

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

JIT 컴파일러는 실행 시점에서 인터프리트 방식으로 기계어를 변환하면서 그 코드를 캐싱하여, 같은 함수가 여러 번 불릴 때 매번 기계어 코드를 변환하는 것을 방지한다.

 

JVM 구성 요소

JVM의 구성요소에는 메모리 관리,클래스 로더, 실행 엔진으로 구성되어 있다.

메모리 관리

  • Java는 자동으로 메모리 관리 기능이 있다. 사용하지 않는 객체를 정리하고 메모리 확보를 위해 백그라운드에서 작동하는 GC(Garbage Collector)가 있다.
  • 메모리에는 Stack과 Heap 두 부분으로 나뉜다.

Stack

  • 스택 메모리는 모든 함수 호출, int, double 등과 같은 원시 타입, 함수의 지역 변수와 참조 변수가 저장되는 프로세스에 할당된 공간이다.
  • 스택의 변수에는 범위가 있다. 예를 들면 전역 변수가 없고 지역 변수만 있다고 한다면, 컴파일러가 메서드 본문을 실행하면 메서드 본문 내에 있는 스택의 개체만 실행할 수 있다. 다른 지역 변수는 범위를 벗어나므로 실행할 수 없다.
  • 스택 메모리는 Thread 하나당 하나씩 할당된다. 스레드 하나가 새롭게 생성되면 해당 스레드를 위한 Stack 또한 함께 생성되며 각 스레드에서 다른 스레드의 Stack 영역에는 접근할 수 없다.
  • 스택 메모리의 크기는 고정되어 있으며 한 번 생성되면 늘어나거나 줄어들 수 없다.

Heap

  • Heap 메모리에서는 모든 Object 타입의 데이터가 할당된다. (모든 객체는 Object 타입을 상속 받는다)
  • Heap 영역의 Obejct를 가리키는 참조 변수가 Stack에 할당된다. 모든 메모리 중 Stack에 쌓이는 것 외에는 모두 Heap에 쌓인다고 할 수 있다.
  • Heap 영역에서 사용하지 않는 객체는 GC에 의해 자동으로 지워진다. 힙 메모리는 세 부분으로 나눌 수 있다.
    1. New Generation
      • 새로 생성된 모든 객체가 할당되는 곳
      • Eden, Survivor1, Survivor2 3가지 부분이 있다
      • 새로 생성된 모든 객체는 Eden 공간에 할당
      • Eden이 가득 차면 minor GC가 발생하고 지워지지 않은 객체는 Survivor1로 이동한 뒤 Survivor2로 이동한다.
      • Survivor1 및 Survivor2에는 minor GC에서 살아남은 객체가 있다. 여기서 살아남은 객체는 Old generation로 이동한다.
      • Old generation는 GC가 덜 수행하므로 Survivor1, Survivor2 공간에서 오래 살아남는 객체만 Old generation로 이동한다.
    2. Old Generation
      • Young Generation보다 크게 할당되며 크기가 큰만큼 Young Generation보다 GC가 적게 발생한다. 여기서 발생한 GC는 Major GC가 발생한다고 한다.
      • Young Generation은 주기적으로 minor GC를 발생시켜 Heap 메모리를 정리하고, 상대적으로 오랜 시간 사용되는 객체를 Old Generation에서 관리한다.
      • Young Generation에 할당된 객체에 Age가 설정된다.
      • 해당 연령이 충족되면 해당 객체는 이전 세대로 이동한다.
    3. Permanent Generation
      • Class 혹은 Method Code가 저장되는 영역
      • Default로 제한된 크기를 갖고 있다.
      • Java8에서 Permanent Generation이 없어지고 MetaSpace가 등장

클래스 로더(Class Loader)

  • Java Compiler를 통해 생성된 .class 파일은 각 디렉토리에 흩어져 있다. 기본적인 라이브러리의 클래스 파일들은 $JAVAHOME_ 내부 경로에 존재한다. 아무튼 Class Loader는 각각의 클래스 파일들을 찾아 JVM의 메모리에 탑재해주는 역할을 한다.
  • JVM에 관련된 일도 하는데 크게 Loading, Linking, Initialization 3가지 역할을 맡는다.
    • Loading은 클래스 파일을 탑재하는 과정
    • Linking은 클래스 파일을 사용하기 위해 검증하고, 기본 값으로 초기화하는 과정
    • Initialization은 static field의 값들을 정의한 값으로 초기화하는 과정
  • 클래스 로더 동작 순서
    1. 로드: 클래스 파일을 가져와 JVM의 메모리에 로드한다.
    2. 검증: 자바 언어 명세 및 JVM 명세에 명시된대로 구성되어있는지 검사한다.
    3. 준비: 클래스가 필요로 하는 메모리를 할당한다. (필드, 메서드, 인터페이스 등)
    4. 분석: 클래스의 상수 풀(Pool) 내 모든 심볼릭 레퍼런스를 다이렉트 레퍼런스로 변경한다.
    5. 초기화: 클래스 변수들을 적절한 값으로 초기화한다. (static 필드)

실행 엔진(Execution Engine)

앞서 JVM에 대해 이야기할 때 Java 언어의 특성에 대해 이야기했다. Java Compiler가 .java 파일을 .class 라는 Java bytecode로 변환한 건 OS가 실행할 수 없다. JVM이 변환된 Java bytecode를 실행시켜준다.

JVM의 실행 엔진은 JVM 메모리에 올라온 Java bytecode를 명령어 단위로 하나씩 가져와 실행한다.

실행 엔진에는 .class 파일을 실행하기 위한 세 가지 주요 구성 요소가 포함되어 있다.

  1. 인터프리터: 바이트 코드를 한 줄씩 읽으면서 OS가 실행할 수 있도록 기계어로 번역한다. 초기 JVM은 인터프리터 방식만 이용하여 한 줄 한 줄 읽기에 실행 속도가 느린 단점이 있지만, JIT 컴파일러를 통해 속도를 보완했다.
  2. JIT 컴파일러(Just-In-Time Compiler): JIT 컴파일러는 반복되는 코드를 기계어로 변환하여 캐싱한다. 캐싱된 기계어는 인터프리터가 해석하는 게 아닌 JIT 컴파일러가 캐시에서 꺼내 바로 실행한다.
  3. GC(Garbage Collector): 메모리를 자동으로 관리하는 Java 프로그램이다. 항상 백그라운드에서 실행된다. 프로그램의 메모리를 관리한다.

 

JDK와 JRE의 차이

  • JDK(Java Development Kit)는 자바 개발키트의 약자로 이름 그대로 개발자들이 자바로 개발하는데 사용된다. JDK 안에는 개발 시 필요한 라이브러리들과 컴파일러, javadoc 등의 개발 도구들을 포함하고 있고, 개발을 하려면 실행 또한 시켜줘야하기 때문에 JRE도 함께 포함하고 있다.
  • JRE(Java Runtime Environment)는 자바 실행 환경의 약자로 Java로 만들어진 프로그램을 실행시키는데 필요한 라이브러리들과 각종 API, JVM이 포함되어 있다. JRE는 자바로 개발은 안되고 실행만 된다라고 생각하면 된다.
  • JDK는 JRE를 포함하고 있다. Java 프로그램을 실행만 할거라면 JRE만 설치하면 된다. Java 프로그래밍을 할거라면 JDK를 설치해야한다.

 

 

https://tjdrnr05571.tistory.com/19

https://tecoble.techcourse.co.kr/post/2021-07-15-jvm-classloader/

https://www.geeksforgeeks.org/execution-engine-in-java/

'Java' 카테고리의 다른 글

5주차 클래스  (0) 2022.08.18
제어문  (0) 2022.08.12
연산자  (0) 2022.08.10
자바 데이터 타입, 변수 그리고 배열  (5) 2022.07.28
자바 프로그램의 개발과 구동  (0) 2022.01.17