[스터디할래 01] JVM은 무엇이며 자바 코드는 어떻게 실행하는 것인가
이 글은 ‘백기선’ 개발자님과 함께하는 온라인 자바 스터디에 참여하여 준비/학습한 내용을 정리하는 글입니다.📚
- 1주차 : https://github.com/whiteship/live-study/issues/1
- 목표 : 자바 소스 파일(.java)을 JVM으로 실행하는 과정 이해하기.
나는 공식문서를 좋아해서, 오라클 Java SE 8 공식문서를 주로 참고하여 정리해보았다.
JVM이란 무엇인가
- JVM은 자바 플랫폼의 주춧돌이다. 하드웨어나 OS에 종속되지 않고 컴파일된 코드를 실행할 수 있음을 보장한다.
- 추상적인 컴퓨팅 머신이다. 실제 컴퓨팅 머신처럼 런타임에 메모리를 관리하는 기술과 instruction set을 갖고 있다.
- JVM은 특정 구현 기술을 의미하는 것이 아니다.
- JVM은 자바 프로그래밍 언어에 대해서는 아무 것도 모른다. 바이터리 포맷인
class
파일만 해석할 수 있다. class
파일에는 JVM instructions, symbol table 같은 것들이 포함되어 있다.- 어떤 언어든 유효한
class
파일로 작성되면 JVM에서 실행할 수 있다. 예를 들어 Jython은 파이썬 언어로 작성한 프로그램을 JVM에서 실행한다.
컴파일 하는 방법
- 대게 일반인이 작성하는 java/python과 같은 프로그래밍 언어는 컴퓨터가 바로 이해할 수 없다. 쉽게 생각하면, 컴퓨터가 이해할 수 있는 언어로 바꿔 주는 과정이 컴파일이다.
- 자바에서는
.java
를.class
파일로 컴파일한다. - 일반적으로 컴파일러는 고수준 언어를 저수준 언어(기계어/어셈블리어 등)로 변환하는 변환기를 일컫는다.
- JDK 내에는 자바 언어로 작성된 컴파일러인
javac
가 포함되어 있다.javac
로java
파일을 JVM이 이해하는class
파일로 컴파일한다.
실행하는 방법
javac
로 자바파일을 컴파일하고,java
로 JVM위에 프로그램을 구동시킨다.- 예)
javac Sample.java
->java Sample
바이트코드란 무엇인가
- 바이트코드에 대해서는 Wikipedia의 설명이 좋아서 가져와보았다.
바이트코드(Bytecode, portable code, p-code)는 특정 하드웨어가 아닌 가상 컴퓨터에서 돌아가는 실행 프로그램을 위한 이진 표현법이다. 하드웨어가 아닌 소프트웨어에 의해 처리되기 때문에, 보통 기계어보다 더 추상적이다. 역사적으로 바이트코드는 대부분의 명령 집합이 0개 이상의 매개 변수를 갖는 1바이트 크기의 명령 코드(opcode)였기 때문에 바이트코드라 불리게 되었다. 바이트코드는 특정 하드웨어에 대한 의존성을 줄이고, 인터프리팅도 쉬운 결과물을 생성하고자 하는 프로그래밍 언어에 의해, 출력 코드의 한 형태로 사용된다. 컴파일되어 만들어진 바이트코드는 특정 하드웨어의 기계 코드를 만드는 컴파일러의 입력으로 사용되거나, 가상 컴퓨터에서 바로 실행된다.
- 자바에서는
class
파일이 바이트코드로 작성되어있고, JVM은 바이트코드를 읽는다. - 기계어가 아닌 중간단계의 코드이다.
JIT 컴파일러란 무엇이며 어떻게 동작하는지
- 프로그램은 결국 하드웨어에서 동작하기 위해 ‘기계어’로 변환되어야 한다.
- JIT(Just In Time) 컴파일은, 프로그램을 실제 실행하는 시점에 기계어로 번역하는 것을 말한다.
- 자바에서는 바이트코드 컴파일러인
javac
에 의해class
로 변환하고, JIT 컴파일러는 바이트코드를 읽어 기계어를 생성한다.
전통적인 입장에서 컴퓨터 프로그램을 만드는 방법은 두 가지가 있는데, 인터프리트 방식과 정적 컴파일 방식으로 나눌 수 있다. 이 중 인터프리트 방식은 실행 중 프로그래밍 언어를 읽어가면서 해당 기능에 대응하는 기계어 코드를 실행하며, 반면 정적 컴파일은 실행하기 전에 프로그램 코드를 기계어로 번역한다. JIT 컴파일러는 두 가지의 방식을 혼합한 방식으로 생각할 수 있는데, 실행 시점에서 인터프리트 방식으로 기계어 코드를 생성하면서 그 코드를 캐싱하여, 같은 함수가 여러 번 불릴 때 매번 기계어 코드를 생성하는 것을 방지한다. (출처 : https://ko.wikipedia.org/wiki/JIT_%EC%BB%B4%ED%8C%8C%EC%9D%BC)
JVM 구성 요소
- 공식 문서에 따르면, JVM 구현스펙은
class
파일만 올바르게 읽으면 된다고 한다. 조금 충격적이지만, GC 알고리즘 등은 사실 JVM의 구현 스펙이 아니라고 한다.To implement the Java Virtual Machine correctly, you need only be able to read the class file format and correctly perform the operations specified therein. Implementation details that are not part of the Java Virtual Machine’s specification would unnecessarily constrain the creativity of implementors. For example, the memory layout of run-time data areas, the garbage-collection algorithm used, and any internal optimization of the Java Virtual Machine instructions (for example, translating them into machine code) are left to the discretion of the implementor.
- 공식문서에서는 JVM의 구조보다 바이트코드인
class
파일의 구성요소에 좀 더 집중되어있어서 Dzone JVM architecture를 참고하였다.
ClassLoader
- 컴파일 시점이 아닌 실행 시점에 클래스 파일을 읽는다.
- BootStrap(
rt.jar
) -> Extension(~/jre/lib
) -> Application(소스코드) 의 순서로 읽는다. - 바이트코드를 읽고
static
변수를 메모리에 할당한다.
Runtime Data Area
- 데이터는 크게 Method, Heap, Stack, PC Registers, Native Method 5가지 영역에 저장된다.
- Method Area : 클래스 레벨의 데이터가 저장된다. 하나의 JVM에 하나만 존재하며, 공유된다.
- Heap Area : 인스턴스 변수가 저장된다. 하나의 JVM에는 힙 영역도 하나만 존재한다. 여러 스레드가 공유하는 공간이다. 기본적으로는 thread-safe 하지 않은 공간이다.
- Stack Area : 하나의 스레드 단위로 생기는 공간이다. 지역변수가 저장되며 thread-safe하다.
- PC Registers : 각 스레드의 현재 실행시점이 저장되는 공간이다. 하나의 스레드 당 개별 공간을 가진다.
- Native Method stacks : 각 스레드마다 native method를 저장하는 공간이다.
Execution Engine
JIT Compiler
- 바이트코드는 JIT Compiler에 의해 런타임 시점에 기계어로 한번에 변환되어 실행된다.
GC
- JVM의 메모리를 정리해준다. 참조를 잃은 메모리 영역을 지우고, 새 주소를 할당할 수 있도록 한다.
JDK와 JRE의 차이
JRE
- Java Runtime Environment
- JVM이 자바 프로그램을 동작시킬 때 필요한 라이브러리 등을 가지고 있음.
JDK
- Java Development Kit
- 자바 프로그램 개발을 위해 필요한 것들을 포함하고있다. 바이트코드 컴파일러인
javac
를 포함하여 JVM을 구동하기 위한 라이브러리 등을 포함한다. - JDK는 JRE를 포함하고 있다.
*- java9부터는 JRE를 따로 배포하지 않는다고 한다. *