Skip to content

Latest commit

 

History

History
220 lines (124 loc) · 8.61 KB

박성우.md

File metadata and controls

220 lines (124 loc) · 8.61 KB

JVM 개요

JVM의 역할

  • JVM은 자바 언어로 작성된 프로그램을 실행하는 역할을 한다.
  • 자바 프로그램은 자바 컴파일러에 의해 바이트코드로 변환되고, JVM은 이 바이트코드를 실행한다.
  • JVM은 플랫폼 독립성을 제공하여, 한 번의 컴파일로 여러 운영체제에서 실행 가능하다.

JVM 아키텍처 및 컴포넌트 개요

  • JVM은 크게 클래스 로더, 실행 엔진, 메모리 영역으로 구성된다.
  • 클래스 로더는 클래스 파일을 JVM으로 로드하고, 메모리에 적재한다.
  • 실행 엔진은 바이트코드를 실행하는 역할을 담당한다.
    • 주요한 실행 엔진 - 인터프리터, JIT 컴파일러
  • 메모리 영역은 프로그램 실행에 필요한 메모리를 관리한다.
    • 주요 영역 - 메소드, 힙, 스택

바이트코드

자바 컴파일러와 바이트코드 생성

  • 자바 소스 코드는 자바 컴파일러에 의해 컴파일되어 바이트코드로 변환된다.
  • 바이트코드는 JVM이 이해할 수 있는 중간 언어로, 기계어에 가깝지만 플랫폼 독립적이다.
  • JVM은 바이트코드를 실행하여 프로그램을 실행한다.

클래스 로딩과 클래스 로더

클래스 로딩 과정과 동적 로딩

  • 클래스 로딩은 JVM이 클래스 파일을 로드하여 메모리에 적재하는 과정이다.
  • 클래스 로더는 로딩, 링크, 초기화의 3단계로 이루어진 로딩 과정을 수행한다.
    • 로딩

      JVM 클래스 파일을 찾아 메모리로 읽어오는 과정이다.

      클래스 로더가 클래스 파일을 검색하고, 해당 클래스 파일을 JVM 내부의 메모리에 로드한다.

      로드된 클래스는 JVM 내부에서 사용될 수 있다.

    • 링크

      로딩된 클래스의 준비 작업을 수행하는 단계이다.

      1. 검증

        로딩된 클래스 파일이 자바 언어 명세에 맞는지 확인한다.

      2. 준비

        클래스의 정적 변수를 메모리에 할당하고 기본 값으로 초기화한다.

      3. 해결

        클래스 레벨에서의 참조를 실제 메모리 상의 참조로 연결한다. 다른 클래스나 메서드에 대한 참조를 실제 메모리 주소와 연결한다.

    • 초기화

      클래스 변수와 클래스의 정적 초기화 블록을 실행하는 단계이다.

      초기화는 클래스의 인스턴스가 생성되기 전에 실행된다.

      정적 초기화 블록은 클래스가 처음으로 로딩될 때 한 번만 실행된다.

클래스 로더의 역할

  • 클래스 로더는 클래스 파일을 검색하고 로드하는 역할을 한다.

메모리 관리

JVM의 메모리 구조

  • JVM은 실행 중인 자바 프로그램에 대한 메모리를 관리한다.
  • 주요 메모리 영역으로 Heap, Stack, Method Area, Native Method Stack 등이 있다.
    • Heap

      동적으로 할당되는 객체 인스턴스와 배열이 저장되는 영역

    • Stack

      메서드 호출과 관련된 정보를 저장하는 영역

      각 스레드마다 별도의 스택이 할당된다.

    • Method Area

      클래스의 구조와 정적 변수를 저장하는 영역

    • Native Method Stack

      자바 코드가 아닌 다른 언어로 작성된 네이티브 메소드 호출 시 사용되는 스택

OutOfMemoryError와 메모리 누수

  • 자바 프로그램에서 메모리가 부족한 상황에서 발생하는 예외
  • 힙 메모리 사용량이 최대치거나, 메모리 누수가 발생한 경우
    • 메모리 누수

      더 이상 필요하지 않은 객체가 메모리에서 회수되지 않고 유지되는 상황을 의미한다.

      이를 방지하기 위해, 가비지 컬렉션 및 메모리 최적화 기법을 적절하게 사용하고, 객체 참조를 관리해야한다.

GC란 무엇인가?

수동으로 메모리를 관리하는건 번거롭고 어려운 일이다..

JAVA에서는 GC가 Heap 메모리에서 unreachable한 객체를 삭제시켜준다.

코드 레벨의 메모리 관리에서 벗어나 편리하다.

장점

  • Memory Leak가 발생되지 않음
  • 휴먼 에러 발생 가능성 낮춤

단점

  • 성능 저하

    어떤 메모리를 해제할지 검사하고 삭제하는 과정 또한 결국 CPU 자원과 메모리를 필요로 한다.

  • 개발자는 언제 메모리가 해제되는지 모른다.

    jvm은 GC를 실행시키기 위해 잠시 application 실행을 멈춘다.

GC 알고리즘

Mark and Sweep

Mark : root set으로부터 Heap 영역의 모든 객체를 스캔한다.

Sweep : root set에서 unreachable한 객체를 Heap 영역에서 제거한다.

  1. 의도적으로 GC를 실행시켜야한다.

image

Young generation : 새로운 객체들이 할당되는 곳

Old generation : Young generation에서 오랫동안 살아남은 객체들이 존재하는 곳
  1. application 실행과 GC 실행이 병행된다.

Generation을 둘로 나누는 이유

  1. 할당된 객체는 오랫동안 참조되지 않는게 대부분이다. 즉, 금방 Garbage 상태가 된다.
  2. 반대로 오래된 객체에서 젊은 객체로의 참조는 거의 없다.
  3. 따라서 Heap이 하나라면 오래된 객체까지 스캔해야해서 비효율적이다.
  4. 차라리 두 영역으로 나눠서 오래된 객체는 따로 빼두고, 할당된지 얼마 안 된 객체들만 주기적으로 스캔하는 게 훨씬 효율적이다.

Young generation

  • eden에 객체가 꽉 차면 minor GC가 실행된다.

    • mark and sweep이 진행된다.
    • reachable이라 판단된 객체는 survival 0 영역으로 옮겨진다.
    • 이 때 옮겨지면서 age-bit가 1 올라간다.
    • age-bit가 특정 숫자만큼 높아지면, old generation으로 옮겨진다.
  • eden이 또 꽉차면?

    • reachable이라고 판단된 객체들이 survival 1 영역으로 이동하며 age-bit가 1 올라간다.
    • survival 0 영역에 있던 친구들도 survival 1 영역으로 이동하며 age-bit가 1 올라간다.

Old generation

generation이 꽉 차면 major GC가 실행되며 mark and sweep을 이용한다.

JVM은 어떻게 GC와 Application을 같이 실행할까?

JVM은 GC를 실행하기 위해 Application 실행을 멈춘다 (stop the world)

stop the world 시간이 짧을 수록 최적화된 것이다.

  1. parallel GC

    여러 개의 thread로 GC를 실행하기 때문에 stop the world 시간이 짧다.

  2. G1 GC

힙 영역을 더 작은 영역인 리전이라는 단위로 분할하여 관리한다.

JIT 컴파일러

Just In Time 컴파일러는 JVM 실행 엔진 중 하나로, 바이트코드를 실제 기계어로 변환하여 실행 속도를 향상시키는 역할을 한다.

JIT 컴파일러 동작 원리

  • 인터프리터 실행
    • 프로그램이 시작되면 인터프리터에 의해 바이트코드가 해석되고 실행된다.
  • 프로파일링
    • 인터프리터는 실행된 코드의 횟수, 타입 등의 정보를 수집하여 프로그램의 실행 특성을 분석한다.
  • 컴파일 타깃 선택
    • 프로파일링 정보를 기반으로, 최적의 성능을 위해 컴파일할 코드를 선택한다.
  • 동적 컴파일
    • 선택된 코드는 JIT 컴파일러에 의해 동적으로 기계어로 변환된다.
  • 기계어 실행
    • 컴파일된 코드는 바로 실행되며, 인터프리터의 해석 단계를 거치지 않고 직접 실행된다.

장점

  • 실행 시간을 개선하여 애플리케이션 성능 향상
  • 코드를 동적으로 최적화하므로, 최적화 수준이 높아진다.

단점

  • JIT 컴파일러의 컴파일 과정이 약간의 오버헤드를 발생시키므로, 초기 실행 시간이 늘어날 수 있다.
  • JIT 컴파일러가 동적으로 생성하는 기계어 코드는 캐시 메모리를 많이 사용하므로, 메모리 사용량이 증가할 수 있다.

GC는 가장 많은 가비지를 가지고 있는 리전을 우선적으로 처리한다. (Garbage-First)

G1 GC는 일시 정지 시간을 최소화하기 위해 병렬 및 동시 가비지 컬렉션을 사용한다.

일시 정지 시간을 분산시켜 애플리케이션의 반응성을 향상시킨다.