Skip to content

Latest commit

 

History

History
155 lines (118 loc) · 11.2 KB

week1.md

File metadata and controls

155 lines (118 loc) · 11.2 KB

목표

JVM은 무엇이며 자바 코드는 어떻게 실행하는 것인가

  • 자바 소스 파일(.java)을 JVM으로 실행하는 과정 이해하기.

학습할 것

JVM이란 무엇인가

JVM이란 Java Virtual Machine 의 줄임말로 write once, run everywhere
즉, OS마다 따로 코드를 작성해야하는 번거로움 없이 '플랫폼, OS에 독립적'으로 Java 프로그램을 작성하여 실행할 수 있게 해주는 표준이자 구현체이다.

이러한 JVM은 어떻게 동작할까?

예를 들어 C 프로그램은 바로 *기계어로 컴파일 되므로 *H/W 에 맞게 각각 컴파일되어야 한다.
    * CPU 제조사에 따라 해석할 수 있는 기계어가 다르기 때문임. -> "C프로그램은 플랫폼에 종속적"
    
그에 반해 Java 프로그램은 CPU가 해석할 수 있는 기계어가 아닌 "JVM이 해석 가능한" 가상 머신용 바이트 코드로 컴파일되어
JVM이 OS 의존적인 부분을 대신 처리하고, "자바 바이트코드를" "OS에 특화된 코드로" 변환하기 때문에 플랫폼에 "독립적"이게 동작할 수 있다. 
    -> JVM은 플랫폼에 종속적
    -> 컴퓨터가 바로 인식할 수 있는 "바이너리 코드"가 아닌 "바이트 코드"로 변환된다.
    
즉 소스파일(*.java)은 컴파일러(javac)에 의해 중간단계 언어(바이트 코드)로 컴파일되고, 
JVM이 이러한 컴파일된 자바 클래스 파일(*.class)을 OS에 맞는 기계어로 변환(인터프리터와 JIT 컴파일러를 통해)하여 실행하는 것이다.
    - JVM을 위한 바이트 코드를 자바 바이트 코드라고 한다.
    - javac.exe : 자바 컴파일러, 자바로 작성된 소스코드를 바이트코드로 변환한다.
    - java.exe : 자바 인터프리터, 바이트코드를 실행한다.
    
* 정리 
1) Java 클래스 파일(.class)을 로드하고
2) 바이트 코드를 해석하며
3) 메모리 등의 자원을 할당하고 관리하며 정보를 처리하는 프로그램

java 프로그램이 실행되는 순서

바이너리 코드란 무엇인가

바이너리 코드는 컴퓨터가 인식할 수 있는 0과 1로 구성된 이진코드를 의미한다.

기계어란?

기계어는 "CPU가 직접 해독하고 실행할 수 있는" 비트 단위(0과 1로 이루어짐)로 쓰인 컴퓨터 언어이다.
기계어가 이진코드로 이루어졌을 뿐이지, 모든 이진코드가 기계어인 것은 아니다. (바이너리 코드 != 기계어)

* 기계어는 특정한 언어가 아니다. 
   - 단지 CPU제조사에서 CPU를 만들 때 해당 CPU에서 사용하는 명령어 집합을 공개하는데, 이것을 '기계어'라고 부를 뿐이다.
   - 때문에 CPU가 변경되면 기계어가 달라진다. 같은 동작을 하는 명령어지만 완전히 다른 0과 1의 나열이 될 수 있다는 말이다. 
   - 같은 회사의 CPU라도 버전 별로 다른 명령을 포함할 수 있으며 다른 회사라도 같은 명령어 집합을 공유할 수도 있다.

바이트 코드란 무엇인가

"CPU"가 이해할 수 있는 언어가 "바이너리 코드"라면, "바이트 코드"는 "가상 머신"이 이해할 수 있는 바이너리 코드이다.
즉 고급언어로 작성된 "소스코드"를 가상 머신이 이해할 수 있는 중간 코드로 컴파일 되어 어떤 플렛폼에도 종속되지 않게
실행될 수 있는 "가상 머신용 기계어 코드"이다.

바이트 코드는 다시 실시간 번역기 또는 저스트 인 타임(just-in-time, JIT) 컴파일러에 의해 네이티브 코드로 변환된다.

컴파일 하는 방법

컴파일이란 우리의 언어는 컴퓨터가 이해하지 못하므로 컴퓨터가 이해할 수 있도록 "통역"하는 작업을 말한다.

1. 자바 컴파일러는 자바 개발 키트(JDK)에 포함되어 있기 때문에 작성된 코드를 컴파일해 바이트코드를 생성하기 위해선 우선 JDK가 필요하다.
2. 자바 언어 사양(JLS)을 충족하는 자바 소스코드(*.java) 파일을 작성한다.
3. 자바 컴파일러(javac.exe)를 통해 자바 소스코드(.java)를 자바 가상 머신 사양(JVMS)을 충족하는 바이트코드(.class)로 컴파일 한다.
    - javac Test.java
4. *.class 파일이 생성된 것을 확인 할 수 있다.
    - Test.class

실행하는 방법

java.exe -> 자바 인터프리터로서 컴파일러로 생성된 바이트 코드를 해석하고 실행한다.
- java Test

역컴파일하는 방법

javap.exe(역어셈블러) -> 컴파일된 클래스 파일을 원래의 자바 소스코드로 변환한다.
- javap Test

JIT 컴파일러란 무엇인가

JIT는 Just-In-Time의 약어로서 그림에서 볼 수 있듯이 JRE(실행엔진)안에 존재해서 프로그램을 실제 실행하는 시점에(런타임시)
해당 플랫폼에 맞는 기계어(native machine code)로 컴파일하는 컴파일 기법이다.

즉 간단히 말해서 JRE안에 존재하면서 프로그램을 실행할 때 기계어로 번역해 전달하는 장치인 것이다.

JIT

그렇다면 이러한 JIT는 왜 쓰이고 어떻게 동작할까?

자바 바이트 코드는 인터프리터 언어(interpreter language)이다. 
인터프리터가 한줄씩 읽고 해석하고, 그에 해당하는 기능을 실행시키는 인터프리터 언어이기에
기기에서 직접 돌아가는 기계어로 컴파일 되는 C/C++와 같은 언어들로 만든 실행 파일에 비하면 실행 속도가 느리다.

이러한 실행 속도를 개선하기 위해 같은 코드를 매번 새롭게 해석하는 대신, 실행하기 전에 미리 JIT 컴파일러를 이용해
"반복적인 코드"를 "네이티브 코드"로 전부 바꿔두고 그 다음부터는 인터프리터가 컴파일된 네이티브 코드를 바로 사용해
인터프리터의 느린 실행 성능을 개선할 수 있다.

단점이라면 JIT 컴파일러가 컴파일하는 과정은 바이트코드를 하나씩 인터프리팅하는 것보다 훨씬 오래 걸리므로 
초기 실행 속도와 메모리 사용량에서 손해를 보는 단점도 있다.
따라서, JIT 컴파일러를 사용하는 JVM들은 내부적으로 해당 메서드가 얼마나 자주 수행되는지 체크하고, 일정 정도를 넘을 때에만 컴파일을 수행한다.

JIT2

JVM 구성 요소

JVM

JVM은 크게 4가지 구성요소로 이루어져 있다.

1. 클래스 로더 시스템
    - 우리가 컴파일한 바이트코드(*.class)를 실행시점(RunTime)에 읽어들여서 메모리(Runtime Data Area)에 적절하게 배치하는 것이 클래스로더의 역할이다.
    - 크게 3가지 로딩 -> 링크 -> 초기화의 순서로 일을 한다.
        - 로딩 : 클래스 로더가 .class 파일을 읽고 그 내용에 따라 적절한 바이너리 데이터를 만들고 메서드 영역에 저장
                로딩이 끝나면 해당 클래스 타입의 Class 객체를 생성하여 “힙" 영역에 저장.
        - 링크 : Verify, Prepare, Resolve 세 단계로 나누어져 있다.
        - 초기화 : static 변수의 값을 할당한다. static 블럭은 이때 실행된다.
    
2. 메모리(Runtime Data Areas)
    - JVM이 프로세스로써 수행되기 위해 OS로부터 할당받는 메모리 영역이다. 목적에 따라 크게 5가지 블럭으로 나뉘어있다.
    - 메소드 영역 : 클래스 수준의 정보 (클래스 이름, 부모 클래스 이름, 메소드, 변수) 저장.
    - 힙 영역 : 객체를 저장한다. 인스턴스들이 다 힙에 저장된다.
    - 스택 영역 : 메소드가 호출될 때마다 스택 프레임이라 불리는 블럭이 하나씩 생성되고 메소드 실행이 완료되면 삭제된다.
    - PC 영역 : 쓰레드 내 현재 실행할 스택 프레임을 가리키는 포인터가 생성된다.
    - 네이티브 메소드 영역 : 다른 언어(C, C++)의 메소드 호출을 위해 할당되는 구역, 언어에 맞게 Stack이 생성된다.
    
    - 메소드 영역, 힙 영역 : 여기에 저장된 정보들은 모든 Thread 공유
    - 스택 영역, PC 영역, 네이티브 메소드 영역 : 쓰레드 마다 생성되어 저장된 정보를 공유하지 않는다.
    
3. 실행 엔진
    - Class Loader를 통해 JVM 내의 Runtime Data Areas에 배치된 바이트코드를 명령어 단위로 읽어서 실행한다.
    - 두가지 방식의 조합을 통해 실행하는데
        - JIT 컴파일러 : 인터프리터의 단점을 보완하기 위해 도입, 위에서 설명
        - 인터프리터 : 바이트코드 명령어를 하나씩 읽어서 해석하고 실행한다. 이 과정에서 바이트코드가 네이티브 코드로 변환된다.
    - GC : 실행엔진의 제일 중요한 부분으로 더이상 참조되지 않는 객체를 모아서 정리한다.

4-1. JNI(Java Native Interface)
    - 자바 애플리케이션에서 C, C++, 어셈블리로 작성된 Native 키워드를 사용한 함수를 사용할 수 있는 방법 제공
4-2. 네이티브 메소드 라이브러리
    - C, C++로 작성 된 라이브러리

JRE란

JRE(Java Runtime Enviroment)란 컴파일된 자바 프로그램을 실행(JRE의 목적)시킬 수 있는 자바 실행 환경으로
JVM + JVM이 자바 프로그램을 동작시킬 때 필요한 핵심 라이브러리 파일 + 자바 런타임 환경에서 사용하는 프로퍼티 세팅이나 리소스 파일을 가지고 있다.
JRE는 *.class 파일을 JVM으로 로딩시키는 역할을 하고, JVM은 *.class 파일을 해석해 실행할 수 있는 상태로 만든다.

jre

JDK란

JDK(Java Development kit)란 자바 애플리케이션 개발 환경으로 
JRE(자바 실행 환경 / JVM) + 소스 파일의 컴파일러 및 디버거 등 자바 애플리케이션을 개발하기 위한 도구(javac, java등)가 포함되어 있다.

즉 JDK를 설치하면 JRE도 같이 설치가 되기 때문에, JDK = JRE + @ 이다.

jdk

전체적인 흐름

자바 소스파일을 Java Complier 가 바이트 코드(*.class)로 변환하고, Class Loader 가 Runtime Data Area 에 클래스 파일을 적재 시킨다.
Execution Engine 이 자바 메모리에 적재된 클래스 들을 기계어로 변환해 명령어 단위로 실행하고
Garbage Collector 는 Heap 영역에 적재된 객체들 중에서 참조되지 않은 객체를 제거한다.