ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [1주차]JVM
    스터디/[whiteship]JAVA 2021. 1. 2. 15:00

    목표

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


    학습할 것

    • JVM이란 무엇인가
    • 컴파일 하는 방법
    • 실행하는 방법
    • 바이트코드란 무엇인가
    • JIT 컴파일러란 무엇이며 어떻게 동작하는지
    • JVM 구성 요소

    JVM이란 무엇인가

    먼저 자바  컴파일러는 자바 소스코드(.java의 확장자)를 .class(바이트 코드)로 변환 시켜준다. (하지만 실행은 어떻게 할까?) -> JVM을 통해서

    JVM(Java Virtual Machine)이란 컴파일러에 의해 생성된 Java Byte Code를 OS에 맞게 해석해 주는 역활을 담당을 하고 GC(Garbase Collection)을 이용해 자동으로 메모리 관리를 한다.

    장점 : Byte코드는 JVM에서 실행되기 때문에 OS에 종속적이지가 않다. (Window, Mac, Linux 등)

    단점 : Byte코드는 기계어가 아니기 때문에 JVM에서 해석을 거쳐 최종적으로 OS에서 실행되는데, 이 부분에 있어 C언와 같은 네이티브 언어에비해 속도가 느리다는 단점이 있다. 하지만 JIT(Just in Time)컴파일러를 구현해 극복했다.

    *잠깐! 여기서 빌드란 컴파일을 포함한 여러가지 과정(testing, inspection, deploy)을 거쳐 프로그램이 실행 할 수 있게 해주는 행위의 집합이다. -> JVM이 실행할 수 있게.

     

    JVM 구성요소

    Class Loader, Runtime Data Areas, Excution Engine로 구성 되어있다.

    즉, 흐름은 클래스 로더가 컴파일된 바이트코드를 -> 런타임 데이터 영역에 로드하고 -> 런타임 영역에선 (OS로부터 메모리를 할당) -> 실행 엔진이 자바 바이트코드를 실행한다.


    1. Class Loader Subsystem

      1) JVM은 메모리(RAM) 영역에 존재한다.

      2) 실행 할 때 Class Loader subsystem을 사용해 Byte코드(.class파일)은 메모리에 적재된다. (이러한 작업을 dynamic class loading이라고 칭함)

      3) 첫번째 실행 시간(run time)에 클래스를 처음으로 참조 할 때 해당 클래스를 로드하고 링크 작업을 수행한다. (컴파일 시간이 아닌 run time에.)

    또한 클레스 로더의 특징을 말하자면 클레스 로더 끼리 부모-자식과 같은 관계를 이루는 계층적 구조를 가지며, 최 상위 클래스 Bootstrap class loader이다. 또한 위임 모델이란 특징을 나타내는데 그림으로 표현하자면 다음과 같다.

    클레스 로더 구조

    클래스 로더가 클래스 로드를 요청받으면, 클래스 로더 캐쉬 ->상위 클래스 로더 -> 자기 자신 순서대로 해당 클래스가 있는지 확인한다. 즉 이전에 로드 되어있던 클래스이면 로더 캐쉬에서 확인하고 없으면 상위 클래스 로더를 거슬러 올라가며 확인한다. 최상위 클래스 로더인 bootstrap class Loader까지 확인해 없다면 다시 요청 받은 클래스 로더(자기 자신)가 시스템에서 해당 클래스를 찾는다.

    (클레스 로더는 요청 받은 상위 클래스 로더는 하위 클래스 로더에서 클래스를 찾을 수 없다는 특징이 있다.)

     

    클레스 로더 구성요소
    Bootstrap class loader  %JAVA_HOME/jre/lib/rt.jar에 담긴 JDK를 로드한다.
    ->JVM을 가동할 때 생성, 최상위 클레스인 object 클래스들을 비롯해 자바 API들을 로드한다 -> 따라서 별도의 import statick java.lang 필요 없이 String객체나 System클레스를 사용할 수 있게 된다.
    Extension Class Loader jre/lib/ext 폴더나 java.ext.dirs에서 환경변수로 지정되 있는 폴더의 클레스들을 로드한다.
    System class loader 우리가 만든 Class를 메모리에 적재한다. -> $CLASSPATH 기준으로.
    User-defined class loader 애플리케이션 사용자가 직접 코드 상에서 생성해 사용하는 클래스 로더.

     

    클래스 로더가 로드할 클래스를 발견하면 다음과 같은 단계를 거쳐 클래스를 로드하고 링크하고 초기화한다.

    로드 : 클래스를 파일에서 가져와 JVM의 메모리에 업로드

    -linking영역-

    검증 : 읽어들인 클래스가 자바와 JVM에 명세에 명시된 대로 잘 구성되어 있는지 검사한다.

    준비 : 클래스가 필요로 하는 메모리를 할당, 클래스에 정의된 필드, 메서드, 인터페이스들을 나타내는 데이터 구조를 준비.

    분석:  class에 다양한 방식으로 참조된 모든 방법들을 로딩한다 -> super class, interface, filed, method signature

    ※method signature - 메소드의 이름, 매개변수와 타입, 갯수를 말하는 용어. 

    초기화: 클래스 변수들을 적절한 값으로 초기화한다. 즉, static initializer들을 수행하고, static 필드들을 설정된 값으로 초기화한다. 

     

    이 단계를 거치면 .class(바이트 코드)를 로딩하고 사용할 준비가 된다. 


    2. Runtime Data Area

    JVM이 프로그램을 수행하기 전에 OS로 부터 별도로 할당 받은 메모리 공간이다.

    1)PC Register : 각 스레드마다 하나씩 존재하며 스레드가 시작될 때 생성되는 영역으로 현재 수행 중인 JVM 명령의 주소를 가지고 있다.

    2)JVM Stack : 이 영역도 마찬가지로 스레드가 시작될 때 생성되며, JVM은 JVM 스택에 스택 프레임을 push, pop동작만 수행한다.

    ●스택 프레임 : JVM내에서 메서드가 수행될 때마다 하나의 스택 프레임이 생성 되는데 해당 스레드의 JVM 스택에 추가된다. 만약 메서드가 종료 된다면 스택 프레임이 제거 된다. 각 스택 프레임은 지역 변수 배열, 피연산자 스택, 현재 실행중인 메서드가 속한 클래스의 런타임 상수 풀에 대한 래퍼런스를 갖는다.

     

    ○지역 변수 배열 : 0부터 시작하는 인데스를 가진 배열로, 0은 메서드가 속한 클래스 인스턴스의 this 레퍼런스이고, 1부턴 메서드에      전달되는 파라미터가 저장된다. 인덱스 1 이후 에는 메서드의 지역 변수들이 저장된다.

        

      피연산자 스택 : 메서드의 실제 작업 공간으로 다른 메서드 호출 결과를 push하거나 pop한다.

     

    3)Native Method StacK : 자바 외의 언어로 작성된 C/C++ 등의 코드를 수행하기 위한 스택으로 언어에 맞게 C 스택이나 C++스택이 생성된다. -> JNI(Java Native Interface)를 통해 C/C++ 등의 코드들을 호출한다.

     

    ★PC register, JVM stack, Native Method stack은 스레드마다 하나씩 생성된다.

     

    4)Method Area : 메서드 영역은 모든 스레드가 공유하는 영역으로 JVM이 시작될 때 Byte코드를 저장하는 영역이다. JVM이 읽어 들인 각각의 클래스 및 인터페이스에 대한 런타인 상수 풀, 필드와 메서드 정보, Static 변수가 여기에 저장.

     

    5)Heap : 가비지 컬렉션의 대상으로 인스턴스 또는 객체를 저장하는 공간이다.

     

    6)런타임 상수 풀 : 각 클래스와 인터페이스의 상수뿐만 아니라, 메서드와 필드에 대한 모든 레퍼런스까지 담고 있는 테이블이다. 

           ->어떤 메서드나 필드를 참조 할 때 JVM은 런타임 상수 풀을 통해 해당 메서드나 필드의 실제 메모리주소를 찾아 참조한다.

     

    ★Method Area, Heap, 런타임 상수 풀은 스레드가 공유해서 사용한다.

     

    Runtime data area : 이 단계를 거치면 OS로 부터 메모리를 할당 받는다.

     

    중간정리

    .java파일을 컴파일로 통해 .class(바이트코드)로 변환한다. 후에 Java ClassLoader를 통해 byte코드를 읽어 들여 메모리를 할당 받는다.

    PC레지스터에는 현재 실행중인 메서드가 byte코드에 어느 부분을 실행하고 있는지에 대한 정보를 가지고 있다. 

    JVM stack 영역에는 각 메서드마다 스텍 프레임을 할당 받는다. 여기서 스텍 프레임의 구성요소는 지역변수배열, 피연산자스택, Frame데이터가 있다. frame데이터에는 이전 프레임에 대한 정보와 상수풀, 현재 메서드에 속한 클래스, 객체에 대한 레퍼런스 정보를 가지고 있다.

    Native Method area는 JVM의 성능을 향상시키기 위해 사용된 C나 C++과 같이 네이티브한 언어의 사용을 위한 스택이다.

    Heap 영역에는 실체 객체값이 저장되어 있는 영역으로 GC의 관리 대상이다.

    Method 영역에는 Byte코드 즉 클레스와 메서드에 대한 정보, static변수와 전역변수


    3. Execution Engine

    클래스 로더를 통해 JVM내 런타임 영역에 배치된 바이트코드는 실행 엔진에 의해 실행된다.

    실행되는 방식

    ○Interpreter 방식 : 바이트코드를 한 줄씩 해석해 실행하는 초기 방식으로 속도가 드리다.

    ○JIT(Just in Time) 방식 : 인터프리터의 단점을 보완하기 위해 도입된 것으로 인터프리터 방식으로 실행하다 어느 한 시점에서 바이트코드 전체를 컴파일하여 네이티브 코드로 변경하고 이후에 인터프리팅 하지 않고 네이티브 코드로 직접 실행하는 방식이다. 네이티브 코드는 캐시에 보관하기 때문에 한 번 컴파일된 코드는 계속 빠르게 수행하게 된다.


    JIT 컴파일러 동작 방식

    JIT 컴파일러는 자바에 기본적으로 설정되어 있는 기능으로 메서드가 컴파일되면 JVM은 해석하지 않고 직접 컴파일 메서드를 호출한다.

    JVM이 처음 시작되면 수천 가지 메서드가 호출 되는데, 각 메서드에 대해 JVM은 사전 정의 된 컴파일 임계 값에서 시작하여 메서드가 호출 될 때마다 감소되는 호출 계수를 정의한다. 호출 계수가 0에 도달하면 메서드에 대한 Just-in-time 컴파일이 시작된다.

    따라서 임계값을 기준으로 자주 사용되는 메서드는 JVM이 시작된 직후에 컴파일되며 사용되지 않는 메서드는 컴파일 되지 않거나 나중에 컴파일 된다. -> 컴파일 된다는건 바이너리코드로 변환 된다는 뜻이다.

    따라서 JVM은 처음 인터프리터 방식으로 한 줄씩 컴파일 -> 메서드마다 정의된 임계값을 통해 특정 시점부터 JIT컴파일러로 네이티브 코드로 변경 -> 캐쉬에 저장하게 되며 이 후 코드는 계속 빠르게 수행된다.

     

     

    컴파일 하는 방법 

    컴파일 이란 .java 파일(자바 문법에 맞게 작성된 코드의 모음)을 .class(바이트 코드)로 변환해 주는 역활을 한다. 즉 사용자가 작성한 java코드를 JVM이 읽수 있게 바이트 코드로 변환해주는 역활을 한다.

    터미널 창에서 javac 명령을 통해 java 파일을 컴파일 할 수 있다.

    java키워드를 통해 컴파일 된 것(.class)을 확인 할 수 있다.

    실행하는 방법

    컴파일 된 .class 파일을 실행하기 위해선 JDK 실행 명령어인 java를 사용해 .class파일을 실행한다.

    바이트코드란 무엇인가

    바이너리코드 -> CPU가 이해할 수 있는 0,1로 구성된 이진코드이다.

    바이트코드란  -> 가상 머신이 이해 할 수 있는 언어로 변환된 자바 소스 코드를 의미하며, 컴파일러에 의해 바이트코드로 변환된 코드들은 1바이트라 자바 바이트 코드라 불리우고 있다. -> 가상머신으로 실행하기 때문에 어떤 운영체제에서도 실행 할 수 있다.

     

    JVM 구성 요소 JDK와 JRE의 차이

    JRE

    JRE(Java Runtime Enviroment)의 약자로 Java 프로그래밍 언어로 작성된 애플리케이션을 실행하기 위해 JVM및  라이브러리(java.util, java,Math)등을 제공한다. 단, 응용 프로그램을 개발을 하기 위한 컴파일 또는 디버거와 같은 도구 및 유틸리티가 포함되어있지 않는다.

    https://jusungpark.tistory.com/13

    JDK

    JDK는 Java Development Kit의 약자로 Java 기반 응용 프로그램을 개발하는데 필요한 컴파일 또는 디버거와 같은 개발 도구와 JRE를 포함하는 키트이다.

     

    [REFERENCES]

    medium.com/@lazysoul/jvm-이란-c142b01571f2

    medium.com/platform-engineer/understanding-jvm-architecture-22c0ddf09722

    d2.naver.com/helloworld/1230

    bloofer.tistory.com/21

     

Designed by Tistory.