목표
자바 소스 파일(.java
)을 JVM으로 실행하는 과정 이해하기
학습할 것
JVM이란 무엇인가
Write once, run anywhere (한 번 작성하면 어디서든 실행된다)
- JVM(Java Virtual Machine) : OS 독립적으로 자바를 실행하기 위한 가상 컴퓨터
- 컴파일된 바이너리 코드(
.class
)를 실행할 때, 프로그램이 실행되는 OS에 맞게 컴퓨터가 해석할 수 있는 기계어로 변환해준다.
- 자바로 작성된 애플리케이션은 모두 JVM에서만 실행된다.
- 장점
- 자바 애플리케이션은 일반 애플리케이션과 달리 JVM하고만 상호작용을 하기 때문에 OS와 하드웨어에 독립적이다.
- 따라서, 다른 OS에서도 프로그램의 변경없이 실행이 가능하다.
- 하지만, 일반 애플리케이션은 OS와 바로 맞붙어 있기 때문에 다른 OS에서 실행시키기 위해서는 애플리케이션을 해당 OS에 맞게 변경해야한다.
- 예) C언어는 OS의 종류가 달라지면 해석하지 못한다.
- 단, JVM은 OS에 종속적이므로 해당 OS에서 실행가능한 JVM이 필요하다.
- Garbage Collection이 있어 개발자가 메모리 할당, 해제를 신경 쓰지 않아도 된다.
- 단점 : 하드웨어에 맞게 완전히 컴파일된 상태가 아니고 실행 시에 해석되기 때문에 속도가 느리다.
- 하지만 요즘엔 JIT 컴파일러와 향상된 최적화 기술이 적용되어서 속도의 격차를 많이 줄였다.
자바 소스 파일을 컴파일 하는 방법
javac *.java
-
자바 소스파일인 .java
를 컴파일하면 바이트코드인 .class
파일로 변환된다.
-
자바 컴파일러인 javac.exe
는 JDK 설치 후 bin 폴더에 존재한다.
-
javac의 옵션
옵션 | 설명 | 예제 | 기본값 |
---|
-classpath, -cp | 클래스패스(실행할 클래스의 위치) 지정 | javac -cp "/Users/home/A.java" | 현재 디렉토리 |
-d | 어디에 클래스파일을 생성할지 지정 | javac -d "/User/home/path" | 현재 디렉토리 |
-encoding | 소스 파일에 사용된 인코딩 지정 | javac -encoding "utf-8" A.java | |
-g | 모든 디버깅 정보를 출력 | javac -g | |
-verbose | 컴파일러가 진행하는 작업을 모두 출력 | javac -verbose | |
-sourcepath | 소스파일 위치 지정 | javac -sourcepath "/User/home/path” | |
-source | 소스파일 자바 버전 지정 | javac -source 1.8 | |
-target | 타겟파일 자바 버전 지정 | javac -target 1.8 | |
-
높은 버전의 클래스파일을 낮은 버전의 JVM으로 실행하고자 하는 경우 에러가 발생한다.
- 해결책
- 첫째,
-target
옵션을 통해 낮은 버전의 클래스파일로 컴파일
- 단, 높은 버전에서 새로 생긴 기능을 사용했다면 컴파일 오류가 발생한다.
- 만약,
.class
파일만 가지고 있다면 두번째 방법을 이용하자.
- 둘째, 현재 자바 버전을 최신버전으로 업그레이드
- 반대의 경우에는 자바가 하위 버전에 대한 호환성을 보장하므로 에러가 발생하지 않는다.
바이트코드란 무엇인가
- 바이트코드 : JVM이 이해할 수 있는 언어인 컴파일된 자바코드(
.class
)를 의미한다.
- 컴파일된 자바코드의 명령어(OP) 크기가 1바이트라서 자바 바이트코드로 불린다.
- 0과 1로 구성된 바이너리 코드이다.
javap -c *.class
를 통해 자바 바이트코드를 사람이 해석할 수 있는 형태로 변환할 수 있다.
- 바이트코드는 다시 실시간 번역기 또는 JIT 컴파일러에 의해 CPU가 이해할 수 있는 바이너리 코드(=이진 코드)로 변환된다.
❓ 기계어란?
- CPU가 이해하는 0과 1로 이루어진 바이너리 코드이다.
- 기계어가 이진코드로 이루어졌을 뿐 이진코드가 기계어인 것은 아니다.
바이트코드를 JVM으로 실행하는 방법
java *.class
- JVM을 실행시키기 위한 프로그램인
.java.exe
는 JDK 설치 후 bin 폴더에 존재한다.
JIT 컴파일러란 무엇이며 어떻게 동작하는지
- JIT(Just-In-Time) 컴파일러 : 프로그램을 실제 실행하는 시점에 바이트코드를 하드웨어의 기계어로 바로 변환해주는 컴파일러
- 동적 번역이라고도 불린다.
- 인터프리터 방식의 단점을 보완하기 위해 도입되었다.
- 인터프리터 방식으로 실행하다가 적절한 시점에 바이트코드 전체를 컴파일하여 기계어로 변경하고, 이후에는 더 이상 인터프리팅 하지 않고 기계어로 직접 실행하는 방식이다.
- 장점 : 기계어는 캐시에 보관하기 때문에 한 번 컴파일된 코드는 빠르게 수행하게 된다.
- 단점 : 인터프리팅에 비해 시간이 훨씬 많이 소모되므로 한 번만 실행되는 코드라면 컴파일하지 않고 인터프리팅하는 것이 유리하다.
- 따라서, JIT 컴파일러를 사용하는 JVM들은 내부적으로 해당 메서드가 얼마나 자주 수행되는지 체크하고 일정 정도를 넘을때에만 컴파일을 수행한다.
JVM 구성 요소
- 클래스 로더
- 런 타임시 처음 참조된 클래스(
.class
)를 동적으로 메모리상의 JVM으로 로드하고 링크하는 역할
- JVM 내의 런타임 데이터 영역에 바이트코드를 배치한다.
- 실행 엔진
- 메모리에 로드된 바이트코드를 실행시키는 역할
- 구성 요소 각각은 스레드로 만들어져 동시에 수행된다.
- 하지만 GC를 수행하는 동안 다른 모든 Thread는 일시정지된다.
- 구성 요소
- 인터프리터
- 실행 엔진은 자바 바이트코드를 명령어 단위로 읽어서 실행한다.
- 단점 : 한 줄씩 수행하여 속도가 느리다.
- JIT 컴파일러
- 인터프리터 방식으로 실행하다가 적절한 시점에 바이트코드 전체를 컴파일하여 기계어로 변경하고, 이후에는 더 이상 인터프리팅 하지 않고 기계어로 직접 실행하는 방식이다.
- 가비지 콜렉터
- 더이상 사용되지 않는 인스턴스를 찾아 메모리에서 삭제한다.
- 런타임 데이터 영역
- 프로그램을 수행하기 위해 OS에서 할당받은 메모리 공간
- 구성 요소
- PC(Program Counter) Register
- 스레드가 시작될 때마다 생성되는 공간으로, 스레드마다 하나씩 존재한다.
- 현재 실행된 스레드의 메소드가 PC Register에 JVM 명령 주소를 저장한다. (JVM 명령은 1 byte의 opcode와 0개 이상의 피연산자로 구성)
- 이때, 현재 실행된 스레드의 메소드가 네이티브 메소드라면 바이트코드의 명령어가 없기 때문에 PC Register가 비어있거나 정의되지 않는다.
- JVM Stack
- 프로그램 실행과정에서 임시로 할당되었다가 메소드를 빠져나가면 바로 소멸되는 특성의 데이터를 저장하는 영역
- 저장하는 요소 : Stack Frameㄹ
- 로컬 변수
- 일부 실행 결과
- 메소드 호출 또는 반환
- 스레드마다 JVM Stack이 존재하며, 스레드가 생성될 때마다 같이 생성된다.
- Native Method Stack
- Java가 아닌 다른 언어(C, C++, 어셈블러 등)로 작성된 코드를 위한 공간
- Java는 하드웨어를 직접 제어하는 기능이 없으므로 C언어 같은 다른 언어의 기능을 빌려 사용한다.
- 네이티브 방식의 메소드(다른 언어로 작성된 코드)가 실행되는 경우 Native Method Stack을 이용한다.
- JNI(Java Native Interface) 기술로 네이티브 메소드들이 바이트 코드로 변환 되면서 저장하게 된다.
- 자바 프로그램이 컴파일되어 생성되는 바이트코드가 아닌 실제 실행할 수 있는 기계어로 작성된 프로그램을 실행시키는 영역
- Method Area (= Class Area, Static Area)
- 클래스 정보를 처음 메모리 공간에 올릴 때 초기화되는 대상을 저장하기 위한 메모리 영역
- 저장되는 데이터
- 멤버 변수 : 멤버변수의 이름, 데이터 타입, 접근 제어자에 대한 정보
- 메소드 : 메소드의 이름, 리턴타입, 매개변수, 접근 제어자에 대한 정보
- 타입 : class인지 interface인지의 여부 저장. Type의 속성, 전체 이름, super 클래스의 전체 이름. (interface이거나 object인 경우 제외된다. 이건 Heap에서 관리한다.)
- Runtime Constant Pool : 별도의 관리영역으로서 상수 자료형을 저장하여 참조하고 중복을 막는 역할을 수행한다.
- Heap
- 객체를 저장하는 가상 메모리 공간
- Class Area에 있는 클래스들을 객체로 생성한다.
- GC에 의해 관리되는 영역이다.
- 구성요소
- Permanent Generation
- 생성된 객체들의 정보의 주소값이 저장된 공간
- 클래스 로더에 의해 로드되는 클래스, 메소드 등에 대한 Meta 정보가 저장되는 영역이고 JVM에 의해 사용된다.
- Reflection을 사용하여 동적으로 클래스가 로딩되는 경우에 사용된다.
- 객체를 통해 클래스의 정보를 분석해 내는 프로그래밍 기법
- 구체적인 클래스 타입을 알지 못해도, 컴파일된 바이트코드를 통해 역으로 클래스의 정보를 알아내어 사용할 수 있다는 뜻이다.
- New/Young Generation(Minor GC)
- 추후 가비지 콜렉터에 의해 사라질 인스턴스들이 있는 영역
- 생멍주기가 짧은 객체가 저장됨
- 구성요소
- Eden : 객체들이 최초로 생성되는 공간
- Survivor0, 1: Eden에서 참조되는 객체들이 저장되는 공간
- Eden 영역에 객체가 가득차게 되면 첫번째 GC가 발생한다.
- Eden 영역에 있는 값들을 Survivor1 영역에 복사하고 이 영역을 제외한 나머지 객체를 삭제한다.
- Old Generation(Major GC)
- 추후 가비지 콜렉터에 의해 사라질 인스턴스들이 있는 영역
- New/Young Area에서 일정시간 참조되고 있는, 살아남은 객체들이 저장되는 공간으로 생멍주기가 긴 객체가 저장된다.
- Minor GC에 비해 속도가 느리다.
JDK와 JRE의 차이
- JDK(Java Development Kit) : JRE + 도구(컴파일러(javac), 자바 디버거(jdb), javadoc, 서로 연관있는 클래스들을 묶어주는 jar, java 등)
- 자바를 사용하기 위해 필요한 모든 기능을 갖춘 자바용 SDK(Software Development Kit)이다.
- JRE(Java Runtime Environment) : JVM + 자바 클래스 라이브러리
최종 정리
Reference
1. [JAVA] JVM이란? 개념 및 구조 (JDK, JRE, JIT, 가비지 콜렉터...)
2. 자바의 정석 3rd Edition, 남궁성 지음
3. [Java-Live-Study] 1주차 - 자바 소스 파일(.java)을 JVM으로 실행하는 과정 이해
4. JVM 메모리 구조 정리