JVM에 대하여..

Choizz·2023년 4월 7일
0

Java

목록 보기
6/11

이번 포스팅은 JVM에 대하여 포스팅해보려고 합니다. 부정확한 정보가 포함되었을 수 있습니다. 이해 부탁드립니다!


JVM이란 무엇일까?

Java Virtual Machine의 약자로 OS에 종속 받지 않고 Java 애플리케이션을 실행시킬 수 있게 하는 가상의 컴퓨터입니다.. JVM은 OS에 의존적이지만 Java는 독립적이게 됩니다.

만약 JVM이 없다면 각 운영체제의 컴파일러를 사용해서 컴파일을 하게 되는데, 그렇게 되면 해당 운영체제(ex.윈도우)에서만 애플리케이션을 실행이 가능합니다. 다른 운영체제에서 실행을 하려면 다른 운영체제(ex.맥os)로 크로스 컴파일하여 그 운영체제에 맞는 실행 파일을 새롭게 만들어야 합니다.

그런데 자바 같은 경우는 각 운영체제에 설치되어 있는 JVM을 이용해 운영체제에 상관없이 실행 파일을 만들어 실행시킬 수 있습니다. .java파일을 자바 컴파일러(javac.exe)가 컴파일하게 되면 .class라는 파일이 생성됩니다. .class파일의 경우 바이트코드(bytecode)로 이루어져 있는데 .class파일의 바이트코드를 JVM이 읽어 각 운영체제에 맞게 실행시킬 수 있습니다.

바이트코드(byte code) : 사용자 언어인 자바와 기계어인 바이너리 코드(binary code)의 중간 언어이다. 바이트코드는 자바 코드를 배포하는 가장 작은 단위이기도 하며, 자바 컴파일러는 자바 소스 코드를 바이트 코드로 컴파일하여 JVM이 이해할 수 있게 한다.


JDK vs JRE vs SDK

  • JRE (Java Runtime Environment)
    - JVM과 라이브러리 등으로 구성(java, javaw, libraries, rt.jar..)
  • JDK (Java Development kit)
    - JRE를 포함하여 컴파일러, 디버거 등이 포함(javac, jar, debugging tools, javap..)
    [출처: https://media.geeksforgeeks.org/wp-content/uploads/20210218150010/JDK.png]
  • SDK (Software Development Kit)
    - 소프트웨어 개발 키트로 제작사가 제공하는 툴. 키트의 요소는 제작사 마다 다르다.

JVM의 구조

클래스 로더(Class Loader)가 컴파일된 자바 바이트코드를 런타임 데이터 영역(Runtime data area)에 로드하고 실행 엔진(Execution Engine)이 자바 바이트코드를 실행시킵니다.[출처 : https://media.geeksforgeeks.org/wp-content/uploads/jvm-3.jpg]

1. 클래스 로더(Class Loader)

자바는 컴파일 타임이 아닌 런 타임일 때 클래스 파일을 loading, linking, initializaion을 진행하여 메모리에 로딩합니다. 이 동적 로드를 담당하는 부분이 JVM의 클래스 로더입니다.

구성

  • 부트스트랩 클래스 로더(Bootstrap ClassLoader)

    • JVM을 가동할 때 생성됩니다.
    • Object 클래스들을 비롯하여 자바 api들을 로드합니다.
    • 자바 코드가 아니라 네이티브 코드로 구현돼있어 자바 코드로는 구현되지 않습니다.
  • 확장 클래스 로더(Extension ClassLoader) :

    • 기본 자바 API를 제외한 확장 클래스들을 로드합니다.
    • JDK 확장 라이브러리에서 다양한 보안, 확장 기능들을 로드합니다.
  • 시스템 클래스 로더(System ClassLoader) :

    • 애플리케이션의 클래스들을 로드합니다.
    • 사용자가 지정한 클래스 패스 내의 클래스들을 로드한다. 사용자가 직접 만든 클래스들을 말한다.
    • Application ClassLoader라고도 합니다.

과정

1) Loading

클래스 로더는 위임 계층 원리(Delegation Hierarchy Principal)를 따릅니다.

  • JVM이 클래스를 받을 때마다 그 클래스가 이미 로드된 것인지를 확인.(클래스 로더 캐시)
  • 만약 메모리에 로드된 것이라면 JVM은 그것을 실행.
  • 만약 메모리에 존재하지 않는다면 JVM은 클래스 로더 서브 시스템(Class Loader Sub System)에 그 클래스를 로드하도록 요청.
  • 그리고 서브 시스템은 애플리케이션 클래스 로더에 그 통제권을 위임.
  • 애플리케이션 클래스 로더는 그 요청을 확장 클래스 로더에 위임하고 확장 클래스 로더는 요청을 다시 부트스트랩 클래스 로더에 위임.
  • 부트스트랩 로더는 부트스트랩 클래스 패스(JDK/JRE/LIB)에서 요청받은 클래스를 찾는다. 만약 있다면 로드하고 없다면 다시 확장 클래스 로더에 요청을 위임.
  • 요청 받은 확장 클래스 로더는 확장 클래스 패스(JDK/JRE/LIB/EXT)에서 그 클래스를 찾고 있다면 로드하고 없으면 애플리케이션 클래스 로더에 위임.
  • 애플리케이션 클래스 로더는 애플리케이션 클래스 패스에서 그 클래스를 찾고 있으면 로드하고 없으면 ClassNotFoundException 예외를 보냄.
    [출처:https://media.geeksforgeeks.org/wp-content/uploads/20190417144207/java_classloader-1024x311.png]

각 클래스 로더는 로드된 클래스들을 보관하는 namespace를 갖습니다. 클래스를 로드할 때 이미 로드된 클래스인지 확인을 위해 namespace에 보관된 Fully Qualified Class Name(이하 FQCN)을 기준으로 클래스를 찾습니다. FQCN이 같더라도 다른 클래스 로더가 로드한 클래스이면 다른 클래스로 간주됩니다.

JVM은 메모리 영역에 저장하는 것은 다음과 같습니다.

  • 로드된 클래스의 FQCN과 그것의 부모 클래스.
  • .class 파일이 Class, Interface, Enum과 관련됐는지 여부.
  • 수정자, 변수, 메소드 정보 등등.

2) Linking

[출처:https://d2.naver.com/content/images/2015/06/helloworld-1230-3.png]

  • Verifying

    • 읽어 들인 클래스가 자바 언어 명세 및 JVM 명세에 명시된 대로 잘 구성돼 있는지 검사.
    • 가장 복잡하고 시간이 오래 걸린다.
  • Preparing

    • 클래스 및 인터페이스에 필요한 클래스 변수 메모리를 할당하고, 이를 기본값으로 초기화.
    • 기본값으로 초기화된 클래스 변숫값들은 뒤의 Initialization 과정에서 코드에 작성한 초깃값으로 변경.
    • 그래서 JVM에 탑재된 클래스 파일의 코드를 작동시키지 않음.
  • Resolving

    • 심볼릭 레퍼런스 값을 JVM의 메모리 구성요소인 메서드 영역의 런타임 환경 풀을 통해 다이렉트 레퍼런스라는 메모리 주소 값으로 변경.
    • 해당 단계의 영향을 받는 JVM Instruction 요소는 new 및 instanceof가 있다.
    • 심볼릭 레퍼런스(symbolic reference): 우리가 코드를 작성하면서 사용한 class, field, method의 이름을 지칭.
    • 다이렉트 레퍼런스(direct reference): 실제 메모리의 주소값.

3) Initialization

  • 클래스 파일의 코드를 읽는 것을 말합니다. Java 코드에서 class와 interface의 값들을 지정한 값들로 초기화 및 초기화 메서드를 실행시킵니다. 이 때, JVM은 멀티 스레딩으로 작동을 하며, 같은 시간에 한 번에 초기화를 하는 경우가 있으므로 초기화 단계에서도 동시성을 고려해야 합니다.

2. Run Time Data Area

자바는 다양한 런 타임 데이터 영역을 정의하고 있고 실행될 동안 그 데이터를 사용합니다. 몇몇 영역(메모리 영역, 힙 영역, 런 타임 상수 풀)은 JVM에 의해 만들어지고 몇몇 영역(JVM 스택, PC Register, 네이티브 메서드 스택)은 실행 중인 스레드에 의해 만들어집니다. JVM에 의해 만들어진 메모리 영역은 jvm이 종료될 때까지 유지되고 스레드에 의해 만들어진 영역은 스레드가 종료되면 없어집니다.

[출처 : https://media.geeksforgeeks.org/wp-content/uploads/jvm-memory-2.jpg]

1) PC Register

  • JVM은 한 번에 많은 스레드가 수행될 수 있게 한다.
  • 각 JVM 스레드는 각각 pc 레지스터를 가지고 있다.
  • 각 스레드는 현재의 메서드를 수행하는데, 만약 메서드가 네이티브(자바로만 이루어 지지 않은 언어)하지 않다면, pc 레지스터는 현재 진행되고 있는 JVM의 인스트럭션 주소를 포함한다.
  • 만약 진행중인 스레드가 네이티브하다면 pc 레지스터의 값은 정의되지 않는다(undefined).
  • pc 레지스터는 JVM인스트럭션 주솟값을 저장할 수 있어야 하고 몇몇 특정 플랫폼의 네이티브 포인터 또한 저장할 수 있는 공간이 확보돼야 한다.

2) JVM Stack

  • 스레드마다 하나씩 있으며 스레드가 시작될 때 생성된다.
  • Stack Frame이라는 구조체를 저장한다.
  • JVM은 오직 스택 프레임을 추가(push)하거 제거(pop)하는 동작만 수행한다.
  • 스택의 크기는 고정될 수도 있고, 동적일 수도 있다.
  • 참조 변수, 매개 변수, 지역 변수, 리턴 값, 연산시 일어나는 값들이 임시로 저장된다.
  • 스택 안의 변수들은 그것들을 만든 메서드가 수행 중일 때만 존재한다.
  • 힙 메모리보다 빠르다.
  • 각자의 스레드의 스택에서 수행되기 때문에 Thread Safe하다.

# Stack Frame

  • 지역 변수 배열, 피연산자 스택, 현재 실행 중인 메서드가 속한 클래스의 런 타임 상수 풀에 대한 레퍼런스를 갖고 있다. 지역 변수 배열과, 피연산자 스택은 컴파일 시 정해지기 때문에 스택 프레임의 크기도 메서드에 따라 고정된다.

    - 지역 변수 배열(Local Variable Array) : 0부터 시작하는 인덱스를 가진 배열로, 메서드에 전달된 파라미터들이 저장되며 그 이후는 메서드의 지역변수들이 저장된다.
    - 피연산자 스택(Operand Stack) : 메서드의 실제 작업공간으로, 각 메서드는 피연산자 스택과 지역 변수 배열 사이에서 데이터를 교환하고 다른 메서드 호출 결과를 추가하거나 꺼낸다.

3) Heap 영역

- 모든 JVM의 스레드에게 공유되며, JVM이 시작할 때 만들어진다.

  • 모든 클래스 인스턴스와 배열이 할당된다.
  • 시스템 구성에 따라 크기가 동적일 수도 있고 고정될 수도 있다.
    - new 키워드를 사용하면 객체는 heap에 저장되고 참조값은 stack에 저장된다.
  • 스택보다 느리다.
  • 스택과 달리 자동으로 할당이 해제되지 않는다. 가비지 컬렉터(GC)가 메모리 사용의 효율성을 위해 사용되지 않는 개체들을 힙에서 비운다.
    • 힙 메모리 구성
      • Young Generation
        - eden space : 새로운 객체가 만들어 질 때, JVM이 이 공간에 할당한다.
        - survivor space : 가비지 컬렉트에서 살아남은 기존 객체들을 포함한다.
      • Old or Tunered Generation
        - 오랫동안 살아남은 객체들이 저장된다. 영 제너레이션에 객체들이 저장될 때 각 객체의 수명의 한계점이 세팅되고 그 한계점에 도달하면 올드 제너레이션으로 이동된다.
      • Permanent Generation
        - 런타임 클래스와 애플리케이션 메서드에 대한 JVM 메타데이터로 구성된다.

4) Method 영역

  • 힙 메모리의 논리적인 부분으로 JVM이 시작될 때 생성된다.
  • 모든 스레드가 공유한다.
  • JVM이 읽어들인 각각의 클래스와 인터페이스에 대한 런타임 상수 풀, 필드와 메서드 정보, static 변수 , 메서드의 바이트코드 등을 보관한다.
  • 시스템 구성에 따라 크기가 고정될 수도 있고 확장시킬 수도 있다.

# 런타임 상수 풀

클래스 파일 포맷에서 constant pool 테이블에 해당하는 영역이다. 메서드 영역에 포함되는 영역이긴 하지만 JVM 동작에서 가장 핵심적인 역할을 수행하는 곳이다. 각 클래스와 인터페이스의 상수 뿐만 아니라 메서드와 필드에 대한 모든 레퍼런스까지 담고있는 테이블이다. 어떤 메서드나 필드를 참조할 때, JVM은 런타임 상수 풀을 통해 해당 메서드나 필드의 실제 메모리상 주소를 찾아서 참조한다.

5) Native Method Stack

  • 자바 외의 언어로 작성된 네이티브 코드를 위한 스택.
  • 스레드마다 동일하게 생성된다.
  • JNI(Java Native Interface)를 통해 호출하는 네이티브 메서드를 위한 스택이다.
  • 네이티브 메서드가 실행될 때 스택에 해당 메서드가 쌓인다.

3. Execution Engine

클래스 로더를 통해 JVM 내의 런타임 데이터 영역에 배치된 바이트 코드는 실행 엔진에 의해 실행됩니다. 실행 엔진은 바이트 코드를 JVM 내부에서 기계가 실행할 수 있도록 네이티브 코드로 변경합니다. 이것을 JIT 컴파일러가 수행합니다. 실행 엔진에는 JIT compilier, Garbage Collector 등이 있다. 여기서는 특히 JIT 컴파일러에 대해 알아보고 GC같은 경우 따로 포스팅을 해야할 것 같습니다.

JIT(Just in Time) compiler

바이트코드를 네이티브 코드로 변환하는 방법은 수행 속도에 커다란 영향을 미친다. 컴파일러지만 프로그램이 수행될 때 적용된다.

  • 인터프리터는 바이트코드의 명령어를 하나씩 읽으면서 해석하고 실행합니다. 이것은 바이트코드를 변환하는 것 자체는 빠르나 인터프리팅한 결과의 실행은 느리다는 단점을 가지고 있습니다.
  • 이것을 보완한 것이 JIT 컴파일러입니다. JIT 컴파일러는 인터프리터 방식으로 실행하다 적절한 시점에 바이트코드 전체를 컴파일하여 네이티브 코드로 변경한다. 이후 해당 메서드를 더 이상 인터프리팅 하지 않고 네이티브 코드로 직접 실행하는 방식이다. 네이티브 코드는 캐시에 보관하기 때문에 한 번 컴파일된 코드는 계속 빠르게 수행되게 된다.
  • JIT 컴파일러는 바이트코드를 IR(Intermediate Representation)로 변환 후 최적화를 수행하고 네이티브 코드를 생성한다.
  • 만약 한 번만 실행되는 코드라면 바이트코드를 컴파일하지 않고 바로 인터프린팅하는 것이 좋다. 왜냐하면 JIT 컴파일러가 컴파일을 하는 것이 훨씬 느리기 때문이다. 그래서 JVM은 해당 코드가 얼마나 자주 수행되는 지를 체크하고, 일정 정도를 넘었을 때 컴파일을 수행한다.

# 핫 스팟?

오라클 핫스팟 VM은 핫 스팟 컴파일러라고 불리는 JIT 컴파일러를 사용합니다. 이것은 내부적으로 프로파일링을 통해 가장 컴파일이 필요한 부분을 찾아낸 다음 이 부분을 네이티브 코드로 컴파일하기 때문에 핫 스팟이라고 불립니다. 이것은 컴파일된 바이트코드라도 자주 사용되지 않는다면 캐시에서 네이티브 코드를 덜어내고 다시 인터프리터 모드로 작동합니다.

  • 핫 스팟 클라이언트 컴파일러 : CPU 코어가 하나 뿐인 사용자를 위해서 만들어진 것이다. 애플리케이션의 시작 시간을 빠르게 하고, 적은 메모리를 점유하도록 한다.
  • 핫 스팟 서버 컴파일러 : 코어가 많은 장비에서 사용된다. 애플리케이션 수행 속도에 초점이 맞춰져있다.

Java 8부터는 신경쓰지 않아도 된다.


레퍼런스

https://www.geeksforgeeks.org/jvm-works-jvm-architecture/
https://www.geeksforgeeks.org/classloader-in-java/
https://d2.naver.com/helloworld/1230
https://doozi0316.tistory.com/entry/1%EC%A3%BC%EC%B0%A8-JVM%EC%9D%80-%EB%AC%B4%EC%97%87%EC%9D%B4%EB%A9%B0-%EC%9E%90%EB%B0%94-%EC%BD%94%EB%93%9C%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%EC%8B%A4%ED%96%89%ED%95%98%EB%8A%94-%EA%B2%83%EC%9D%B8%EA%B0%80
https://tecoble.techcourse.co.kr/post/2021-07-15-jvm-classloader/
https://search.shopping.naver.com/book/catalog/32506068618?
자바의 신 (저자 이상민)

profile
집중

0개의 댓글