자바 소스 파일(.java)을 JVM으로 실행하는 과정 이해하기.
- JVM이란 무엇인가
- 컴파일 하는 방법
- 실행하는 방법
- 바이트코드란 무엇인가
- JIT 컴파일러란 무엇이며 어떻게 동작하는지
- JVM 구성 요소
- JDK와 JRE의 차이
JVM이란 무엇인가
- JVM은 자바 가상 머신으로 자바 바이트코드를 실행할 수 있는 주체이다.
- 자바로 작성된 애플리케이션은 모두 JVM 에서만 실행되기에, 자바 애플리케이션이 실행되기 위해서는 JVM이 반드시 필요하다.
- 일반 애플리케이션 코드는 OS만 거치고 바로 HW로 전달되는데에 비해 java 애플리케이션은 JVM을 한 번 더 거치고, 실행 시 해석(interpret) 되기 때문에 속도가 느리다는 단점이 있다.
- 하지만 컴파일된 자바코드인 바이트코드를 HW의 기계어로 바로 변환해주는 JIT 컴파일러 기술로 인해 속도가 줄어들게 되었다.
JVM의 메모리 구조
메서드 영역
- 프로그램 실행 중 어떤 클래스가 사용되면, JVM은 해당 클래스의 클래스 파일을 읽어 분석하여 클래스에 대한 정보와 그 클래스의 변수를 이곳에 저장
힙
- 인스턴스가 생성되는 공간
- 프로그램 실행 중 생성되는 인스턴스들이 모두 여기에 저장
- 인스턴스 변수들이 생성되는 공간
호출스택
- 메서드의 작업에 필요한 메모리 공간
- 메서드가 호출되면, 호출된 메서드를 위한 메모리가 할당되며 이 메모리는 메서드가 작업을 수행하는 동안 지역변수들과 연산의 중간결과들을 저장하는데 사용
- 메서드가 작업을 마치면 할당된 메모리는 반환
JVM의 동작 과정
- 프로그램이 실행되면 JVM은 OS로 부터 메모리를 할당받는다.
- 자바 컴파일러가 자바 소스코드를 읽어들여 자바 바이트코드(.class)로 변환시킨다.
- Class Loader를 통해 class 파일을 JVM으로 로딩한다.
- 로딩된 class파일들은 Execution engine을 통해 해석된다.
- 해석된 바이트코드는 Runtime Data Area에 배치되어 실질적 수행이 이루어지게 된다.
- 위의 실행과정 속에서 JVM은 필요에 따라 Thread Synchronization, GC와 같은 관리작업을 수행
컴파일 하는 방법
- IDE를 통해 컴파일 / CMD로 직접 컴파일
1. java 파일 작성
2. javac 로 해당 파일 컴파일
옵션 | 설명 | 예제 |
-classpath, -cp | 클래스패스, 즉 실행할 클래스의 위치를 지정한다. | javac -cp "/Users/home/A.java" |
-d | 어디에 클래스파일을 생성할지 지정한다. | javac -d "/User/home/path" |
-encoding | 소스 파일에 사용된 인코딩을 지정한다. | javac -encoding "uft-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 ~~~ |
실행하는 방법
java javaTest
내부 실행 과정
- 클래스 로더 - 해당 클래스 파일을 메모리상의 JVM으로 가지고 온다.
- Byte Code Verifier - 바이트코드 변조 확인
- Execution Engine - 메모리에 적재된 클래스들을 기계어로 변경해 명령어 단위로 실행
- 명령어 단위 실행 방식은 Interpreter 방식 / JIT(Just In Time compiler) 방식 두 가지로 나뉘어진다
바이트코드란 무엇인가
- 가상머신 (JVM)이 인식하기 쉬운 코드
- 바이트코드는 저스트 인 타임(just-in-time, JIT) 컴파일러에 의해 바이너리 코드로 변환됨.
- 바이트코드 직접 살펴보기
Classfile /C:/Users/daseul/Desktop/javaTest.class
Last modified 2021. 6. 24; size 418 bytes
MD5 checksum 3e7a1685f7fa8d2d3a0a6d2f18a14542
Compiled from "javaTest.java"
public class javaTest
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #6.#15 // java/lang/Object."<init>":()V
#2 = Fieldref #16.#17 // java/lang/System.out:Ljava/io/PrintStream;
#3 = String #18 // JavaTest
#4 = Methodref #19.#20 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = Class #21 // javaTest
#6 = Class #22 // java/lang/Object
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 main
#12 = Utf8 ([Ljava/lang/String;)V
#13 = Utf8 SourceFile
#14 = Utf8 javaTest.java
#15 = NameAndType #7:#8 // "<init>":()V
#16 = Class #23 // java/lang/System
#17 = NameAndType #24:#25 // out:Ljava/io/PrintStream;
#18 = Utf8 JavaTest
#19 = Class #26 // java/io/PrintStream
#20 = NameAndType #27:#28 // println:(Ljava/lang/String;)V
#21 = Utf8 javaTest
#22 = Utf8 java/lang/Object
#23 = Utf8 java/lang/System
#24 = Utf8 out
#25 = Utf8 Ljava/io/PrintStream;
#26 = Utf8 java/io/PrintStream
#27 = Utf8 println
#28 = Utf8 (Ljava/lang/String;)V
{
public javaTest();
descriptor: ()V
flags: 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 1: 0
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String JavaTest
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 3: 0
line 4: 8
}
SourceFile: "javaTest.java"
Constant pool
- 해시(#)은 참조하고 있는 constant pool 인덱스를 표시한다.
- Methodref는 메소드의 참조를 나타낸다.
#4 = Methodref #19.#20 // java/io/PrintStream.println:(Ljava/lang/String;)V
- constant pool은 소스코드에서 클래스와 그 멤버를 참조할 수 있도록 하는 역할이다.
Default Constructor
- 컴파일러가 디폴드 생성자를 자동으로 생성해줌
public javaTest();
descriptor: ()V
flags: 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 1: 0
JIT 컴파일러란 무엇이며 어떻게 동작하는지
- 실행 엔진 - 클래스 로더를 통해 런타임 데이터 영역에 배치된 바이트코드를 명령어 단위로 읽어서 실행
- 바이트코드를 기계어로 번역하여 실행
- 사용 방식은 인터프리터 / JIT 으로 나눠진다.
인터프리터
- 바이트코드 명령어를 하나씩 읽어 해석하고 실행
- 하나하나의 해석은 빠르지만 전체적인 실행속도는 느림
- JVM 안에서의 바이트코드는 기본적으로 인터프리터 방식으로 동작함
JIT 컴파일러
- 인터프리터의 단점을 보완하기 위해 도입된 방식
- 바이트코드 전체를 컴파일하여 네이티브 코드로 변경하고 이후 해당 메서드를 더이상 인터프리팅 하지 않고 네이티브 코드로 직접 실행하는 방식
- 전체적인 속도 향상
- JVM은 내부적으로 해당 메서드가 얼마나 자주 호출되고 실행되는지 체크하고 일정 기준을 넘었을때만 JIT 컴파일러를 통해 컴파일하여 네이티브 코드를 생성
- 같은 코드를 매번 해석하지 않고 실행할 때 컴파일을 하면서 해당 코드를 캐싱한다. 이후에는 바뀐 부분만 컴파일하고 나머지는 캐싱된 코드를 사용하기 때문에 인터프리터의 속도를 개선할 수 있다.
JVM 구성 요소
Class Loader
- 자바 컴파일러가 .java 파일을 컴파일 하면 .class 파일(바이트코드)가 생성된다.
- 이렇게 생성된 클래스 파일들을 엮어 Runtime Data Area 형태로 메모리에 적재하는 역할을 한다.
클래스 로딩을 위한 JVM의 절차
- 어떤 메소드를 호출하는 문장을 만났는데, 그 메소드를 가진 클래스 바이트코드가 아직 로딩된 적이 없다면, 곧바로 JVM은 JRE 라이브러리 폴더에서 클래스를 찾는다.
- 없다면, CLASSPATH 환경변수에 지정된 폴더에서 클래스를 찾는다.
- 찾았다면 그 클래스 파일이 올바른지 바이트코드를 검증한다.
- 올바른 바이트코드라면 메소드영역으로 파일을 로드한다.
- 클래스 변수를 만들라는 명령어가 있으면 메소드 영역에 그 변수를 준비한다.
- 클래스 블록이 있으면 순서대로 그 블록을 실행한다.
- 이렇게 한 번 클래스의 바이트코드가 로딩되면 JVM이 종료될 때 까지 유지된다.
Execution Engine
- 메모리에 적재된 클래스들을 기계어로 변경해 명령어 단위로 실행하는 역할
- 명령어를 하나하나 실행하는 인터프리터 방식과 실행 시점에 자주 쓸만한 코드를 기계어로 변환 시켜놓고 저장해서 사용하는 JIT 방식이 있다.
Garbage Collector
- Heap 메모리 영역에 생성된 객체들 중 Reachability를 잃은 객체를 탐색 후 제거하는 역할
- 가비지 : 정리되지 않은 메모리, 유효하지 않은 메모리 주소
Stop The World
- GC 실행을 위해 JVM이 애플리케이션의 실행을 멈추는 것
- GC 실행 쓰레드 외 모든 쓰레드가 작업을 멈춘다.
Minor GC와 Major GC
- JVM의 Heap은 Young, Old, Perm 세 영역으로 나뉨
- Young 영역에서 발생한 GC - Minor GC
- Old, Perm 영역에서 발생한 GC - Major GC
- Young 영역 : 새롭게 생성한 객체가 위치, 대부분 객체가 금방 unreachable 상태가 되기 때문에 많은 객체가 Young 영역에서 생성되었다가 사라진다.
- Old 영역 : Young 영역에서 reachable 상태를 유지해 살아남은 객체가 여기로 복사된다.
- Perm 영역 : Method Area 라고도 한다. 클래스와 메소드 정보와 같이 자바 언어 레벨에서는 거의 사용되지 않는 영역이다.
reachable 상태 : Stack 에서 Heap 영역의 객체를 참조할 수 있느냐
Runtime Data Area
Method Area
- 클래스 멤버 변수, 메소드 정보, 타입정보 (Class or Interface), Constant Pool, static, final 변수 등이 생성된다. 상수 풀은 모든 Symbolic Reference를 포함하고 있다.
Heap Area
- 동적으로 생성된 오브젝트와 배열이 저장되는 곳으로 GC의 대상이 되는 영역이다.
Stack Area
- 지역변수, 파라미터 등이 생성되는 영역
- 동적으로 객체를 생성하면 실제 객체는 Heap에 할당되고 해당 레퍼런스만 Stack에 저장된다.
- Heap에 있는 오브젝트가 Stack에서 참조할 수 없는 경우 GC의 대상이 된다.
PC Register
- 현재 쓰레드가 실행되는 부분의 주소와 명령을 저장
Native Method Stack
- 자바 외 언어로 작성된 네이티브 코드를 위한 메모리 영역
JDK와 JRE의 차이
JDK
- Java Development Kit
- 자바로 된 언어를 컴파일하고 개발할 수 있도록 해주는 개발 환경 세트
- JRE에 컴파일러(javac), 디버거 등 개발도구를을 포함하는 프로그램
JRE
- Java Runtime Enviroment
- 자바 실행 환경
- JVM에 자바 라이브러리와 기타 파일들이 결합된 자바를 실행하기 위한 프로그램
- 자바 9버전 부터 X
- 포함되어 있는 폴더
bin/ | Java 실행 프로그램 JVM을 시작하는 java (Window는 javaw) keytool 및 policytool과 같은 다른 유틸리티 포함 |
conf/ | 사용자가 편집할 수 있는 구성파일 |
lib/ | 여러가지 supporting 파일 일부 .jar 구성파일 속성파일 / 글꼴 / 번역 인증서 등 .class 파일을 포함하는 모듈 |
1. JAVA 언어로 개발을 한다.
컴파일 하고 디버깅 하고 하려면 JDK가 필요하다.
2. 개발을 하면서 자바에서 기본으로 제공하는 JRE 라이브러리를 사용한다.
3. 여러 가지 환경 파일도 JRE가 가지고 있지만 개발자가 직접 다룰 일은 거의 없다.
4. 실행하면 JVM에서 .class파일을 읽어서 바이너리코드를 검증하고 OS 환경에 적합하게 실행해준다.
※ 어쨌든 JDK안에 모두 다 들어 있다. "JDK > JRE > JVM"
※ 개발이 아닌 JAVA로 만들어진 프로그램을 실행하기 위해서는 JRE만 있으면 된다.(JVM 포함)
※ 같은 코드를 짜면 OS별로 최적화되어 있는 JVM이 알아서 환경에 맞게 실행해준다.
'Back-end > java' 카테고리의 다른 글
스터디 3주차 - 연산자 (0) | 2021.06.28 |
---|---|
스터디 2주차 - 자바 데이터 타입, 변수 그리고 배열 (0) | 2021.06.28 |
자바 입출력 (0) | 2021.06.24 |
예외 처리 (0) | 2021.06.24 |
스트림 (0) | 2021.06.23 |