공부방/JAVA

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

EVO. 2023. 7. 14. 00:51

JVM이란 무엇인가

JVM(Java Virtual Machine)은 자바를 실행하기 위한 가상 컴퓨터(가상머신) 입니다.

자바로 작성된 애플리케이션은 모두 이 JVM에서만 실행되기 때문에, 자바 애플리케이션이 실행되기 위해서는 반드시 JVM이 필요합니다.


컴파일하는 방법 (How to Compile Java Program?)

저희가 작성한 자바 프로그램을 컴파일하려면 먼저 본인 컴퓨터 환경에 JDK(Java 개발 키트)가 설치되어 있어야 합니다.

JDK에 대해서는 뒤에서 설명하며 JDK 17를 사용한다는 가정하에 시작하겠습니다(JDK버전 차이는 나중에 다루겠습니다)

우선 저희가 어떤 자바프로그램을 IntelliJ나 Eclipse ide에서 작성했다고 가정합시다. 그리고 작성된 프로그램은 TestProgram.java 파일입니다.

이 자바파일은 순수하게 저희가 읽기 쉬운 코드로만 적혀있습니다. 예를 들면 이런 거죠

public class TestProgram {
    public static void main(String args[]) {
        System.out.println("Hello World");
    }
}

이 파일은 컴퓨터가 읽고 이용할 수 있을까요? 당연히 아닙니다. 무조건 컴퓨터가 읽을 수 있는 하드웨어의 기계어로 바꿔야 합니다. 

java 프로그램을 컴파일하기 위해서는 다음과 같은 명령어가 필요합니다.

javac TestProgram.java

javac 명령어는 먼저 소스파일(. java 파일)을 읽습니다. 소스파일을 읽은 후 javac 명령은 소스파일을 클래스 파일(. class 파일)로 컴파일합니다. 이렇게 변환된. class 파일은 JVM에서 실행됩니다.

 

다시 말하면, javac 명령은 클래스 및 인터페이스 정의를 읽고 JVM에서 실행할 수 있는 .class파일 또는 바이트 코드로 컴파일한다고 말할 수 있습니다.

더보기

소스 파일은 .java 접미사로 끝나야 하며 클래스 파일은 .class 접미사로 끝나야 합니다.

소스 파일의 이름은 main() 메서드가 있는 클래스의 이름 또는 공용 클래스 이름과 동일해야 합니다.

 

한 가지 명심해야 할 점은 모든 Java 애플리케이션에는 반드시 main() 함수가 포함되어야 합니다.(void main(String args[])).

자바 컴파일러는 메인 메서드에서만 코드 컴파일을 시작합니다. 메인 메서드는 Java 애플리케이션의 시작점 또는 진입점이라고 말할 수 있습니다.

 

 


컴파일된  자바프로그램을 실행하기

이제 자바 프로그램을 컴파일하는 방법을 알았으니, 이제 경로변수(path variables)를 임시 또는 영구적으로 설정하는 방법과 자바 프로그램을 실행하는 방법에 대해 알아보겠습니다.

 

자바프로그램을 컴파일할 때 javac명령어를 사용했습니다. 그러나 때때로 javac 명령을 사용하려고 할 때, javac이 내부 또는 외부 명령, 작동 가능한 프로그램 또는 배치 파일로 인식되지 않는다는 오류가 발생합니다.

 

출처 : https://chillyfacts.com/javac-not-recognized-internal-external-command-operable-program-batch-file/

이 오류는 시스템에 경로 변수 또는 자바 경로가 설정되어 있지 않을 때 발생합니다. 쉽게 말해, JDK 설치 폴더의 bin 디렉터리에 존재하는 javac.exe 실행 파일이 PATH 환경 변수에 추가되어 있지 않다고 할 수 있습니다.

 

다음 경로에 javac 실행파일이 있는데 

해당 경로를 아래와 같이 환경변수에 추가해 줘야 cmd에서 javac 명령어를 쓸 수가 있습니다.

 

여기서 저희는 새로운 점을 알 수가 있습니다. JDK에는 javac라는 컴파일 명령 프로그램(javac.exe)이 들어 있는 것입니다.

그래서 저희는 JDK를 무조건 설치를 해야 하고 경로도 설정을 해야 하는 것이죠.

시스템의 PATH 변수에 Java를 추가하기 전까지는 Java 프로그램을 컴파일하고 실행할 수 없습니다.

 

임시 경로를 설정

1. 경로를 찾습니다

  • 먼저 cmd를 엽니다
  • JDK를 설치한 디렉터리 또는 폴더로 이동합니다.
  • JDK에서 bin 폴더를 검색하여 엽니다.
  • 마지막으로 bin 폴더의 위치를 복사합니다. 

제 위치는 다음과 같습니다.

C:\Program Files\Java\jdk-17\bin

 

2. 임시 경로를 설정합니다.

  • 복사한 경로를 cmd에서 set 합니다. 다음과 같은 명령어를 사용합니다.
set PATH = C:\Program Files\Java\jdk-17\bin

 

영구 경로를 설정

해당 사이트에 정말 자세히 나와있으니 그대로 따라 하시면 됩니다.

https://gaeggu.tistory.com/2

 

java 설치하기 ( JDK 설치, 환경변수 설정, 테스트)

이번 포스팅에서는 자바를 설치하는 방법을 알아보겠습니다. 자바 설치는 3단계로 나누어 진행하겠습니다. 1. JDK 설치 2. 환경변수 설정 3. 자바 실행 테스트 1. JDK 설치 먼저 자바를 설치하기 위

gaeggu.tistory.com

 

참고: Mac OS X에서 경로를 설정하려면 아래 단계를 따르세요: 터미널을 열고, export JAVA_HOME=/Library/Java/Home 명령을 입력한 다음 Return 키를 누릅니다.

echo $JAVA_HOME 명령을 사용하여 경로를 확인할 수 있습니다.

 

 

자바 실행하기

이제 환경 변수의 경로를 설정했으므로 자바 소스 코드 파일을 컴파일하고 실행할 수 있습니다.

 

컴파일을 하면 .class파일을 볼 수 있습니다. .class파일에는 클래스의 각 필드와 메서드를 설명하는 테이블이 포함되어 있습니다. 또한 이 파일에는 각 메서드에 대한 바이트 코드, 정적 데이터 및 Java 객체를 나타내는 데 사용되는 설명이 포함되어 있습니다. 여기서는 class파일에 대한 자세한 설명은 들어있지 않습니다.

 

컴파일된 파일을 실행하려면 다음 명령을 사용하면 됩니다:

java TestProgram

바이트코드란 무엇인가

개발자가 작성한 .java 파일을 컴파일(javac)을 통해 JVM이 이해할 수 있도록 하는 Bytecode로 변환하고 .class 파일을 만드는 것을 의미하는데, .class 파일에 존재하는 데이터가 바로 자바 바이트코드, Java Bytecode입니다.

이 자바 바이트코드는 JVM을 위한 명령어 집합입니다.

  • 자바 컴파일러에 의해 변환되는 코드의 명령어 크기가 1byte라서 자바 바이트 코드라고 불립니다
  • 자바 바이트 코드는 자바 가상 머신만 설치되어 있다면 어느 운영체제에서도 실행 가능합니다.

자바 바이트코드 확인하기

먼저 자바 코드를 작성합니다.

public class TestContentJava {
    public static void main(String[] args){
        System.out.println("hello");
    }
}

class Person{
    String name;
    Integer age;

    public Person(String name,Integer age){
        this.name = name;
        this.age = age;
    }
}

 

이제 이 자바파일을 컴파일합니다.

 

하나의 파일을 생성했지만 두 개의 클래스를 작성했기 때문에 아래와 같이 클래스 파일이 2개 생깁니다.

 

이제 TestContent.class파일을 HexViewer로 확인하면 아래와 같이 Byte 형태로 볼 수가 있습니다.

 

하지만 이 Byte 형태로는 저희가 읽기가 어렵습니다. 따라서 javap로 디컴파일하고 보기 편하게 바꿔줍니다.

javap -v -p -s TestContentJava.class

 

그럼 결과로 아래와 같이 나옵니다.

$ javap -v -p -s TestContentJava.class
Classfile /C:/Users/mazin/OneDrive/▒▒▒▒ ȭ▒▒/TestContentJava.class
  Last modified 2023. 7. 14.; size 429 bytes
  SHA-256 checksum 916f35b3e4b80b2a3f89c5749a3504cbeb08a941d74f7d07824715748af51817
  Compiled from "TestContentJava.java"
public class TestContentJava
  minor version: 0
  major version: 61
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #21                         // TestContentJava
  super_class: #2                         // java/lang/Object
  interfaces: 0, fields: 0, methods: 2, attributes: 1
Constant pool:
   #1 = Methodref          #2.#3          // java/lang/Object."<init>":()V
   #2 = Class              #4             // java/lang/Object
   #3 = NameAndType        #5:#6          // "<init>":()V
   #4 = Utf8               java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Fieldref           #8.#9          // java/lang/System.out:Ljava/io/PrintStream;
   #8 = Class              #10            // java/lang/System
   #9 = NameAndType        #11:#12        // out:Ljava/io/PrintStream;
  #10 = Utf8               java/lang/System
  #11 = Utf8               out
  #12 = Utf8               Ljava/io/PrintStream;
  #13 = String             #14            // hello
  #14 = Utf8               hello
  #15 = Methodref          #16.#17        // java/io/PrintStream.println:(Ljava/lang/String;)V
  #16 = Class              #18            // java/io/PrintStream
  #17 = NameAndType        #19:#20        // println:(Ljava/lang/String;)V
  #18 = Utf8               java/io/PrintStream
  #19 = Utf8               println
  #20 = Utf8               (Ljava/lang/String;)V
  #21 = Class              #22            // TestContentJava
  #22 = Utf8               TestContentJava
  #23 = Utf8               Code
  #24 = Utf8               LineNumberTable
  #25 = Utf8               main
  #26 = Utf8               ([Ljava/lang/String;)V
  #27 = Utf8               SourceFile
  #28 = Utf8               TestContentJava.java
{
  public TestContentJava();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #13                 // String hello
         5: invokevirtual #15                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 5: 0
        line 6: 8
}
SourceFile: "TestContentJava.java"

디컴파일 이해 사진: 

Java Bytecode의 구성요소

위를 이해하려면 컴파일 수준을 설명해야 하지만 이 글의 목적성과 떨어지므로 생략하겠습니다.

 


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

JIT(Just-In-Time) 컴파일러는 런타임 시점 바이트코드를 네이티브 머신 코드로 컴파일하여 Java™ 애플리케이션의 성능을 개선하는 런타임 환경의 구성 요소입니다.

 

바이트코드를 해석하는 것은 실행 속도에 영향을 미칩니다. 성능을 개선하기 위해 JIT 컴파일러는 런타임에 JVM과 상호작용하여 적절한 바이트 코드를 네이티브 머신 코드로 컴파일합니다. JIT컴파일러를 사용하면 하드웨어가 네이티브 코드를 실행할 수 있으므로 JVM이 동일한 바이트코드를 반복적으로 해석하는 오버헤드가 발생하지 않고 최적화를 진행합니다.

 

자바 바이트 코드는 인터프리터 언어로 한 줄씩 읽고 해석하며 기능을 실행하기에 C/C++ 같은 언어로 만들어진 실행파일보다 훨씬 느립니다. 이러한 이유로 같은 코드는 매번 새롭게 해석하는 대신, JIT를 이용해 '반복적인 코드'를 '네이티브 코드'로 전부 바꾸고(캐싱) 그다음부터 인터프리터가 바로 네이티브 코드를 사용해 속도를 개선합니다. 그리고 JIT컴파일러와 인터프리터 스레드는 동시에 같이 실행되고 있습니다. 

 

-컴파일 순서는 대략적으로 다음과 같습니다.

TestCode.java => compiler(javac) => ByteCode => (Run Time) JIT Compiler => Native Machine Code

 


JVM 구성 요소

MyApp.java 파일을 만듭니다. javac MyApp.java를 이용해 MyApp.class 파일을 생성합니다.

그리고 java MyApp  명령을 통해 클래스를 호출하면 JVM 인스턴스가 생성됩니다. 이제 JVM 인스턴스가 어떻게 클래스 파일을 로드하고 실행하는지 살펴보겠습니다.

 

JVM 내부 구조

각각의 구성요소에 대한 설명은 다음 글에 정리를 하였으니 참고 바랍니다.

https://babgeuleus.tistory.com/82

 

JVM 내부 구조

다음 다이어그램은 JVM 사양을 준수하는 Java 가상 머신의 주요 내부 구성 요소를 보여줍니다. Class Loader 와 JVM에 의해 할당된 메모리 영역인 Runtime Data Areas는 각각 아래에서 설명하겠습니다. ClassLo

babgeuleus.tistory.com


JDK와 JRE의 차이

 

JDK

JDK(Java Development Kit)는 Java 애플리케이션 및 애플릿을 개발하는 데 사용되는 소프트웨어 개발 환경입니다.

플랫폼 별 소프트웨어로 Windows, Mac, Unix 시스템용 설치 프로그램이 따로 있습니다. Java 개발자는 Windows, macOS, Solaris 및 Linux에서 이를 사용하여 Java 프로그램을 코딩하고 실행할 수 있습니다. 

 

JDK에는 JRE(Java Runtime Environment)와 인터프리터, 컴파일러, 아카이버 및 document generator와 같은 개발도구가 포함되어 있습니다.

 

 

JRE

JRE(Java Runtime Environment)은 JVM을 구현한 것으로, Java 프로그램을 실행할 수 있는 환경을 제공하기 위해 특별히 설계되었습니다. 컴파일러, 디버거 등과 같은 개발 도구는 포함되어 있지 않습니다. 코드 개발이나 컴파일이 필요하지 않으므로 프로그램 실행만 원하는 경우 JDK가 아닌 JRE만 설치하면 됩니다. 

 

JVM

 JVM(Java Virtual Machine)은 JDK와 JRE 모두에 포함되거나 내장되어 있기 때문에 매우 중요한 부분입니다. JRE 또는 JDK를 사용하여 실행하는 Java 프로그램은 모두 JVM으로 이동하며, JVM은 자바 프로그램을 한 줄씩 실행하는 역할을 담당하므로 인터프리터라고도 합니다.

 

자바 9버전 이후 부터는 JRE가 없습니다. JRE를 따로 배포하지 않습니다. 

 

 

전체 그림

출처 : jeongjin984.github.io/posts/JVM/


참고