기록하자..
JVM은 무엇이며 자바 코드는 어떻게 실행하는 것인가 | 백기선님 LIVE-STUDY 본문
학습할 것
- JVM 이란 무엇인가
- 컴파일 하는 방법
- 실행하는 방법
- 바이트코드란 무엇인가
- JIT 컴파일러란 무엇이며 어떻게 동작하는가
- JVM 구성요소
- JDK와 JRE의 차이
JVM 이란 무엇인가
자바 프로그램은 완전한 기계어가 아닌 중간 단계의 바이트코드이기 때문에 운영체제 위에서 바로 실행할 수 없다. 이를 실행하기 위해서 어떤 가상의 운영체제가 필요한데 이를 JVM(Java Virtual Machine)이라고 한다.
:dart: 컴퓨터가 이해할 수 있는 언어는 기계어로 이루어진 바이너리 코드이다.
운영체제별로 프로그램을 실행시키는 방법이 다르기 때문에 각 운영체제(Window, Linux, Mac 등등)에서 동일한 실행결과가 나올 수 있도록 운영체제와 바이트코드(.class)사이에 JVM을 두어 동일한 실행결과를 보장한다. 이로써 바이트코드는 모든 JVM에서 동일한 결과를 보장한다. 하지만 JVM은 운영체제에 종속적이다. 자바 프로그램을 운영체제가 이해할 수 있는 기계어로 번역해서 실행해야하기 떄문에 JVM은 운영체제에 맞게 설치되어야 한다.
결국 JVM은 자바프로그램을 다양한 운영체제에서 이식성 문제없이 도와주는 가상 머신이다.
컴파일 하는 방법
자바 프로그램을 실행하기 위해서는 컴파일 과정이 필요하다. 다음과 같은 Hello.java파일이 있다고 해보자.
public class Hello {
public static void main(String[] args) {
System.out.println("Hello java");
}
}
이를 터미널에서 다음과 같은 명령어로 컴파일을 할 수 있다.
javac Hello.java
이를 실행시키면 Hello.class파일이 생성되는 것을 확인할 수 있다.
javac는 다양한 옵션을 제공하는데 다음과 같다.
- -classpath : 컴파일시 파일 경로를 지정해주는 옵션
- -d : 클래스파일을 생성할 루트 디렉토리를 설정해주는 옵션
- -encoding : 소스파일에 사용될 인코딩 방식을 설정해주는 옵션 (예를 들어 UTF-8, EUC-JP..)
- -g : 모든 디버깅 정보를 생성해주는 옵션, 디폴트로 라인넘버와 소스파일의 정보만 생성해준다.
- -parameters : 생성된 클래스 파일의 생성자나 메서드들의 파라미터들의 이름을 저장해서 Reflection API의
java.lang.reflect.Executable.getParameters메서드가 검색할 수 있도록 해준다.
실행하는 방법
위에서 만든 .class파일을 실행하기 위해서는 다음과 같은 명령어를 사용한다.
java Hello
위에서 컴파일을 통해 만들어진 바이트코드는 JVM위에서 실행되어야 하는데, JVM을 구동시키는 명령어는 java.exe이다. 주의할 점은 java.exe로 바이트코드 파일을 실행할 때는 .class파일에서 확장명을 제외한 이름을 입력해야 한다는 것이다.
바이트코드란 무엇인가
바이트코드는 .class 파일안에 있는 코드이다. 자바 컴파일러에 의해 변환되는 코드의 크기가 1byte이기 때문에 바이트코드라고 불린다. 바이너리 코드이기 때문에 일반적으로 읽을 수는 없고, javap 커맨드를 통해 읽어야 한다. javap를 통해 위에서 만든 Hello.class 파일을 읽어보자.
javap Hello.java
Compiled from "Hello.java"
public class Hello {
public Hello();
public static void main(java.lang.String[]);
}
기본적으로 위 결과는 private접근자에 대한 필드/메서드를 포함하지 않는다.
javap 커맨드에 -c 옵션을 주게 되면 .class 파일을 역어셈블링한다. 결과를 확인해보자.
Compiled from "Hello.java"
public class Hello {
public Hello();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String Hello java
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
}
JIT 컴파일러란 무엇이며 어떻게 동작하는가
JAVA는 소스코드를 컴파일한 후 바이트코드를 얻게 되고 이는 소스코드보다 간단하고 압축되어 있다. 하지만 프로세서는 바이트코드를 바로 실행시킬 수 없는 단점이 있다.
바이트코드를 실행시키기 위해서는 JVM이 바이트코드를 한 줄 한 줄 읽어서 기계어로 번역해서 실행시켜야 했다. 하지만 일반적으로 인터프리터는 실제 프로세서보다 코드를 실행시키는데 있어서 느리기 때문에 JVM은 가장 자주 실행되는 코드 블록, 메서드 혹은 메서드의 일부, 특히 가장 자주 실행되는 반복문을 모니터링해서 네이티브 코드로 컴파일을 시켜놓는다.
이렇게 코드의 일부를 네이티브 코드로 캐싱해놓음으로써 인터프리터로 동작하는 것보다 빠르게 동작할 수 있는 것이다.
JIT 컴파일 과정은 별도의 스레드에서 실행된다. 따라서 JVM 스레드는 JIT 컴파일과정에 영향을 받지 않고, JIT 컴파일과정은 애플리케이션 실행에 영향을 주지 않는다. JVM은 컴파일 중에는 JVM은 인터프리터 방식으로 컴파일하지만, 일단 컴파일이 완료되면 JIT-컴파일된 버전을 사용하게 된다. 이 과정을 on-stack replacement(OSR)이라고 한다.
JVM 구성요소

위의 그림을 보면 크게 3가지 시스템으로 나누어져 있는 것을 확인할 수 있다.
- 클래스 로더 시스템
- .class 파일에서 바이트코드를 읽고 저장한다.
- 이름 그대로 클래스 파일을 로드하는데 사용하는 시스템이다.
- 로딩, 링크, 초기화 과정으로 이루어져 있다.
- 로딩
- 클래스로더가 .class파일을 읽고 내용에 따라 적절한 바이너리 데이터를 생성해
메서드 영역에 저장한다. - 로딩이 끝난 클래스 파일은 Class 객체로
힙 영역에 저장된다.
- 클래스로더가 .class파일을 읽고 내용에 따라 적절한 바이너리 데이터를 생성해
- 링크
- 레퍼런스를 연결하는 과정이다.
- Verify, Prepare, Resolve(Optional) 세 단계로 나누어져 있다.
- Verify: .class이 유효한지 검사한다.
- Prepare: 클래스변수(와 static 변수)의 메모리가 할당되고 기본 값으로 정의된다.
- Resolve: 심볼릭 메모리 레퍼런스를 메서드 영역에 있는 실제 레퍼런스로 교체한다. (symbolic references란 참고하는 클래스의 특정 메모리 주소를 참조 관계로 구성한 것이 아닌 참조하는 대상의 이름만을 지칭한 것.)
- 초기화
- static 변수의 값이 기본 값으로 할당된다.
- 로딩
- 런타임 데이터 영역(메모리)
- Method area : 모든 클래스 수준의 정보(클래스 이름, 부모 클래스 이름, 메서드, 변수)가 저장된다. JVM에 단 하나의 메서드 영역이 있기 떄문에 공유 자원이다.
- Heap area : 객체가 저장된다. 메서드 영역과 마찬가지로 공유 자원이다.
- Stack area : 모든 쓰레드마다 런타임 스택을 만든다. 메서드가 호출될 때마다 스택 프레임이라 부르는 블럭으로 쌓는다. 쓰레드를 종료하면 런타임 스택도 사라진다.
- PC Register : 각 쓰레드가 PC Register를 가진다. 현재 instruction의 위치를 가리키는 포인터가 생성된다.
- Native method stacks : 네이티브 메서드의 정보를 가지고 있다.
- 실행 엔진
- 인터프리터 : 바이트 코드를 한 줄씩 실행한다.
- JIT 컴파일러 : 인터프리터의 효율을 높이기 위해 JVM이 자주 사용되는 코드를 모니터링 해두었다가 JIT 컴파일러로 반복되는 코드를 모두 기계어로 바꾼다. 그 후 인터프리터는 해당 코드를 발견하면 바로 기계어로 번역된 코드를 실행한다.
- GC(Garbage Collector) : 더 이상 참조되지 않는 객체들을 모아 정리한다.
JNI와 네이티브 메서드 라이브러리가 있는데 Native 언어를 위한 영역이다.
JDK와 JRE의 차이
- JDK : JRE와 개발에 필요한 툴을 제공한다.
- JRE : JVM과 라이브러리를 가지고 있고, 자바를 실행하기 위한 최소한의 환경이다.
참고자료
'자바' 카테고리의 다른 글
| 클래스 | 백기선님 LIVE-STUDY (0) | 2022.05.02 |
|---|---|
| 제어문 | 백기선님 LIVE-STUDY (0) | 2022.05.02 |
| 자바가 제공하는 다양한 연산자 | 백기선님 LIVE-STUDY (0) | 2022.03.24 |
| 자바 데이터 타입, 변수 그리고 배열 | 백기선님 LIVE-STUDY (0) | 2022.03.24 |
| JVM (0) | 2021.12.23 |