른록노트
[Java] JVM은 무엇이며 자바 코드는 어떻게 실행하는 것인가 본문
목표
자바 소스 파일(.java)을 JVM으로 실행하는 과정 이해하기.
학습할 것
-
JVM이란 무엇인가
-
컴파일 하는 방법
-
실행하는 방법
-
바이트코드란 무엇인가
-
JIT 컴파일러란 무엇이며 어떻게 동작하는지
-
JVM 구성 요소
-
JDK와 JRE의 차이
JVM이란 무엇인가
JVM(자바 가상 머신)이란 자바 프로그램이 실행되는 가상머신입니다.
자바는 일반 실행 파일처럼 OS에서 바로 실행되는게 아니라 자바 프로그램을 실행하기 위한 가상머신, 즉 JVM에서 실행할 수 있습니다. 이러한 이유때문에 OS에 종속되지 않는다는 장점을 가지고 있습니다.
JDK(자바 개발 도구)를 이용하여 자바 프로그램이 실행되는 과정을 간단히 설명하면 크게 컴파일 타임과 런타임으로 나눌 수 있습니다.
< 컴파일타임 >
.java 확장자의 자바 소스파일을 작성하고 컴파일러를 통해 소스파일을 컴파일해서 .class 확장자의 바이트코드로 쓰여진 바이너리 파일을 생성합니다.
< 런타임 >
컴파일된 파일을 JDK의 bin/java로 실행하면 JVM에서 바이트코드를 실행합니다.
컴파일타임과 런타임의 대한 자세한 설명은 학습할 내용들을 설명하면서 자세히 알아보겠습니다.
* 오라클 공식 홈페이지에서 JVM에 대한 개요는 아래와 같이 설명하고 있습니다.
적응형 컴파일러: 표준 인터프리터는 응용 프로그램을 시작하는데 사용합니다. 애플리케이션이 실행되면 코드를 분석하여 성능 병목 현상 또는 핫스팟을 감지합니다. Java HotSpot VM은 성능 향상을 위해 코드의 성능에 중요한 부분을 컴파일 합니다. 그러나 거의 사용되지 않는 코드(대부분의 응용 프로그램)는 컴파일(JIT)하지 않습니다. Java HotSpot VM은 적응 컴파일러를 사용하여 인라인(인터프리터)과 같은 기술로 컴파일된 코드를 최적화 하는 방법을 결정합니다.
* 인터프리터와 JIT 컴파일러의 대한 내용을 읽은 후 위의 내용을 다시 읽으면 이해하는데 도움이 됩니다.
신속한 메모리 할당 및 가비지 수집: Java HotSpot 기술은 객체 및 빠르고 효율적인 최신 가비지 수집기에 대한 빠른 메모리 할당을 제공합니다.
* Runtime Data Areas와 GC의 대한 내용을 읽은 후 위의 내용을 다시 읽으면 이해하는데 도움이 됩니다.
스레드 동기화: Java HotSpot 기술은 대규모 공유 메모리 다중 프로세서 서버에서 사용하기 위해 확장하도록 설계된 스레드 처리 기능을 제공합니다.
Oracle JRE (Java Runtime Environment) 8 및 이전 버전에서는 클라이언트, 서버 및 임베디드 시스템으로 일반적으로 사용되는 구성에 대해 서로 다른 JVM 구현 (the client VM, server VM, and minimal VM)이 지원되었습니다.
이제 대부분의 시스템에서 서버 VM을 활용할 수 있으므로 해당 VM 구현만 이후 버전에서 제공됩니다.
* JAVA를 실행할 때의 실행 속도 등 영향을 주는 부분이 하드웨어 성능의 향상 때문에 스펙이 변경되었습니다.
컴파일하는 방법 (컴파일타임)
compile이라는 단어의 의미는 "엮다"입니다. 즉, 내가 만든 프로그램 코드를 컴퓨터가 이해할 수 있도록 엮어주는 작업이 바로 컴파일입니다.
자바에서 컴파일이란 .java라는 확장자로 되어 있는 소스파일을 컴파일하여 .class라는 확장자를 가진 파일이 생성됩니다. 이 class 파일은 바이트코드로 이루어진 파일입니다. (바이트 코드는 아래에서 설명하겠습니다.)
컴파일하는 방법은 먼저 텍스트 에디터로 HelloWorld.java 라는 소스파일을 만들고 저장합니다. (C:/HelloWorld.java)
public class HelloWorld {
public static void main(String[] args){
System.out.println("Hello World");
}
}
그리고 터미널을 이용해 C:/로 이동하여 아래 명령어를 입력하면 컴파일이 되고
같은 경로에 HelloWord.class라는 파일이 생깁니다.
C:\> javac HelloWorld.java
컴파일 시 주의할 점은 JDK의 버전에 따라 바이트코드가 다르게 만들어지는데 상대적으로 낮은 JDK 버전에서 컴파일해서 만든 바이트코드는 상위 JDK의 JVM에서 실행되지만 그 반대의 경우는 따로 옵션을 주어야 실행 할 수 있습니다.
아래는 javac의 대표적인 옵션입니다. 다른 옵션들은 공식 문서를 보고 확인하시기 바랍니다.
--class-path path, -classpath path, or -cp path : 클래스패스를 지정합니다. 참조할 라이브러리의 위치
ex) javac -cp C:/javalib C:/HelloWorld.java
--source release or -source release : 지정된 Java SE 릴리스의 Java 프로그래밍 언어 규칙에 따라 소스 코드를 컴파일합니다.
--target release or -target release : 지정된 Java SE 릴리스에 적합한 클래스 파일을 생성합니다. (대상 릴리스는 소스 릴리스와 같거나 더 높아야합니다. (--source 참조))
ex) javac --source 1.8 --target 1.8 HelloWorld.java
(오라클 공식 문서 참조 : docs.oracle.com/en/java/javase/15/docs/specs/man/javac.html#options)
실행하는 방법 (런타임)
정상적으로 컴파일이 완료되면 생서된 HelloWord.class로 자바 프로그램을 실행시킬 수 있습니다.
아래 명령어를 입력하면 자바 프로그램이 실행됩니다.
C:\> java HelloWorld
컴파일부터 실행까지 일련의 결과는 아래와 같습니다.
PS C:\> javac HelloWorld.java
PS C:\> java HelloWorld
Hello World
PS C:\>
자바를 실행할 때에도 관련 옵션이 있습니다.
아래는 bin/java의 대표적인 옵션입니다. 다른 옵션들은 공식 문서를 보고 확인하시기 바랍니다.
-Xms size : 힙의 최소 및 초기 크기 (바이트)를 설정합니다 (메모리 설정).
-Xmx size : 힙의 최대 및 초기 크기 (바이트)를 설정합니다 (메모리 설정).
ex)java -Xms 512m -Xmx 1g HelloWorld
(오라클 공식 문서 참조 : docs.oracle.com/en/java/javase/15/docs/specs/man/java.html)
바이트코드란 무엇인가
바이트코드란 JVM이 이해할 수 있는 코드입니다. 자바 컴파일러에 의해 변환되는 코드의 명령어 크기가 1바이트라고 하여 바이트코드라고 불립니다. bin/javap 파일을 이용하여 class파일의 바이트코드를 볼 수 있습니다. 여기서 나오는 코드를 OP Code(Operation Code) 즉 명령 코드라고 불리고 수행하는 명령어를 나타내는 부호이고 기계어의 일부입니다.
ex) javap -c HelloWorld
JIT 컴파일러란 무엇이며 어떻게 동작하는지
JIT 컴파일러(Just-In-Time compiler)란 JVM 구성요소 중 Excution Engine에 속한 컴파일러입니다. 런타임시 JIT 컴파일러와 인터프리터를 조합하여 바이트코드를 실행하는데, JIT 컴파일러는 런타임에 자주 사용하는 바이트코드를 실제 기계어로 변환하여 캐싱해서 사용할 수 있게 해주어 빠른 속도로 바이트코드를 실행할 수 있게 만들어줍니다.
JVM 구성 요소
JVM은 크게 4가지 구성요소로 이루어져 있습니다.
클래스 로더 시스템
컴파일한 바이트코드를 실행시점(RunTime)에 읽어들여서 메모리(Runtime Data Area)에 적절하게 배치하는 것이 클래스로더의 역할입니다.
자세한 내부 로직은 크게 로딩 -> 링킹 -> 초기화 순서로 진행됩니다.
로딩: 클래스 로더가 .class 파일을 읽고 그 내용에 따라 적절한 바이너리 데이터를 만들고 메서드 영역에 저장합니다. 로딩이 끝나면 해당 클래스 타입의 Class 객체를 생성하여 "힙" 영역에 저장합니다.
링크: Verify, Prepare, Resolve 세 단계로 나누어져 있습니다.
초기화: static 변수의 값을 할당하고 static 블럭은 이때 실행됩니다.
메모리(Runtime Data Areas)
JVM이 프로세스로써 수행되기 위해 OS로부터 할당받는 메모리 영역입니다. 목적에 따라 크게 5가지 블럭으로 나뉘어있습니다.
스택 영역: 메소드가 호출될 때마다 스택 프레임 블럭이 하나씩 생성되고 메소드 실행이 완료되면 삭제됩니다.
PC 영역 : 쓰레드 내 현재 실행할 스택 프레임을 가리키는 포인터가 생성됩니다.
네이티브 메소드 영역 : 다른 언어(C, C++)의 메소드 호출을 위해 할당되는 구역, 언어에 맞게 Stack이 생성됩니다.
힙 영역: 객체를 저장한다. 인스턴스들이 다 힙에 저장됩니다.
메소드 영역: 클래스 수준의 정보 (클래스 이름, 부모 클래스 이름, 메소드, 변수) 저장됩니다.
*스택 영역, PC 영역, 네이티브 메소드 영역 : 쓰레드 마다 생성되어 저장된 정보를 공유하지 않습니다.
*메소드 영역, 힙 영역 : 여기에 저장된 정보들은 모든 Thread 공유합니다
실행엔진(Execution Engine)
Class Loader를 통해 JVM 내의 Runtime Data Areas에 배치된 바이트코드를 명령어 단위로 읽어서 JIT 컴파일러와 인터프리터를 조합하여 실행합니다.
JIT 컴파일러: 인터프리터의 단점을 보완하기 위해 도입하였습니다.
인터프리터: 바이트코드 명령어를 하나씩 읽어서 해석하고 실행합니다. 이 과정에서 바이트코드가 네이티브 코드로 변환됩니다.
GC(가비지 컬렉터): 더이상 참조되지 않는 객체를 모아서 정리합니다.
JNI(Java Native Interface)
자바 애플리케이션에서 C, C++, 어셈블리로 작성된 Native 키워드를 사용한 함수를 사용할 수 있는 방법 제공합니다.
(네이티브 메소드 라이브러리 - C, C++로 작성 된 라이브러리)
JDK와 JRE의 차이
JDK는 JRE를 포함한 자바 개발자 킷입니다. JDK 9버전 부터는 JRE를 따로 제공하지 않고 JDK 하나로 제공하고 있습니다.
JRE는 자바 프로그램을 실행하기 위한 JVM과 관련된 자바 클래스 라이브러리를 포함한 자바 런타임 환경입니다.
참고문헌
- 이상민(2017). 자바의 신 2. 서울: 로드북
- Oracle(2020). Java Virtual Machine Guide. 2021. 01. 21 검색, docs.oracle.com/en/java/javase/15/vm/java-virtual-machine-technology-overview.html
- 위키(2020). 바이트코드. 2021. 01. 21 검색, ko.wikipedia.org/wiki/%EB%B0%94%EC%9D%B4%ED%8A%B8%EC%BD%94%EB%93%9C
- TCP School(연도미상). 자바프로그래밍. 2021. 01. 21 검색, tcpschool.com/java/java_intro_programming
- 2021 .01 .21 검색, github.com/Chohongjae/javaStudy/blob/main/live-study/week1.md
- 2021 .01 .21 검색, gblee1987.tistory.com/173