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이 내부 또는 외부 명령, 작동 가능한 프로그램 또는 배치 파일로 인식되지 않는다는 오류가 발생합니다.
이 오류는 시스템에 경로 변수 또는 자바 경로가 설정되어 있지 않을 때 발생합니다. 쉽게 말해, 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
영구 경로를 설정
해당 사이트에 정말 자세히 나와있으니 그대로 따라 하시면 됩니다.
참고: 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
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를 따로 배포하지 않습니다.
전체 그림
참고
- How to Compile Java Program?
https://www.scaler.com/topics/how-to-compile-java-program/ - 바이트 코드란
https://wonit.tistory.com/589 - Just In Time Compiler
https://www.geeksforgeeks.org/just-in-time-compiler/ - Differences between JDK, JRE
https://www.geeksforgeeks.org/differences-jdk-jre-jvm/
'공부방 > JAVA' 카테고리의 다른 글
JUnit5을 이용한 테스트코드 작성 방법 - 2편 (0) | 2023.08.05 |
---|---|
JUnit5을 이용한 테스트코드 작성 방법 - 1편 (0) | 2023.08.05 |
연산자 - 백기선 자바라이브스터디 (0) | 2023.07.28 |
자바 데이터 타입, 변수 그리고 배열 - 백기선 자바라이브스터디 (0) | 2023.07.22 |
JVM 내부 구조 (0) | 2023.07.15 |