[Java] JDK, JVM, JRE

Updated: Categories:

자바의 동작원리에 대해 알아보자

Java 구성요소

JDK

  • Java Development Kit, 자바 개발도구
  • 컴파일러(javac), 디버그 도구를 포함

JRE

  • Java Runtime Environment, 자바 실행환경 (JVM 실행환경을 구현)
  • 필수 자바 라이브러리 포함

JVM

  • Java Virtual Machine, 자바 가상머신
  • OS별로 존재함 (OS에 종속적임)
  • 컴파일된 바이트 코드를 읽어 실행함


Java 버전

  • 8, 11, 17 버전은 LTS(Long Term Support) 버전이다.

Java 8 특징

  • Lambda
  • Functional interface
  • Default method
  • Stream
  • Optional
  • 새롭게 추가된 날짜 API
    • 이전에는 날짜 계산이 쉽지 않았음
    • LocalDate, LocalTime, LocalDateTime
  • CompletableFuture
  • JVM의 변화
    • Heap영역에 저장되던 PermGen 영역을 native 영역으로 이동

Java 11 특징

  • String 관련 메소드 추가
  • File 관련 메소드 추가
  • Predicate, static not 메소드 추가
  • java 실행 (javac로 컴파일x)
  • garbage collector 추가

~17 특징

  • switch문 확장(12)
  • instanceof 강화(16)
  • Record(16)


Java 동작원리

Class Loader

  • 클래스 파일을 로드하고 링크를 통해 배치하는 모듈
  • 클래스들은 동적로딩됨 (실행시 모든 클래스가 로드되는게 아닌 필요한 시점에 로딩)

Runtime Data Area

  • JVM의 메모리 영역으로 5가지 영역으로 분리된다.
  • Method와 Heap 영역만이 공유 메모리 영역이다.
  • Method
    • 클래스, 변수, 메소드, static 변수, constant pool 정보가 저장되는 영역
    • 클래스가 로드될때 할당됨
  • Heap
    • 동적할당된 객체와 배열이 저장되는 영역
    • 런타임시 할당됨
  • Stack
    • 지역변수, 매개변수, 리턴값 등의 임시값이 생성될때 저장되는 영역
    • 메소드 호출시마다 개별 스택 프레임이 생성되고, 종료시 해제됨
    • 컴파일시 할당됨
  • PC register
    • 현재 스레드가 실행되는 부분의 주소와 명령을 저장
    • 스레드가 생성될때 할당됨
  • Native Method Stack
    • 자바 외 언어로 작성된 네이티브 코드를 위한 메모리 영역(JNI)

Execution Engine

  • JVM 메모리 영역의 바이트 코드를 명령어 단위로 실행함
  • 실행방법에는 두가지가 있음
    • 인터프리터 : 바이트코드를 하나씩 읽어서 실행 (느림, 초기에 사용)
    • JIT(Just-In Time Compiler) : 바이트코드 전체를 바이너리 코드로 컴파일 후 실행 (빠름)
  • 자바는 어떻게 동작하나?
    • 처음엔 인터프리터 방식으로 바이트코드 -> 바이너리로 변환해 실행
    • 해당 코드는 JIT에서 캐싱하고, 중복 코드 발생시 인터프리터가 동작하지 않고 JIT가 바로 캐싱코드를 사용
    • JIT는 인터프리터 방식을 보조한다고 생각하면 됨

Java 컴파일과정

image

  • 다른 컴파일 언어는 컴파일러가 OS에 종속적이지만, Java는 모든 OS에서 동일한 Java 컴파일러를 가진다
    • 동일하게 컴파일된 바이트 코드를 각 OS별 JVM에서 번역을 해주는 방식
  • 컴파일 순서
    1. java 코드 작성
    2. 컴파일러(javac)가 java 파일을 컴파일
      • 바이트 코드(.class) 생성
    3. 바이트코드를 Class Loader에 올림
    4. 클래스를 Runtime Data Area에 올림
    5. Execution Engine이 JVM 메모리에 올라온 바이트 코드를 실행


컴파일 언어 vs 인터프리터 언어

컴파일 언어

  • 실행 전 빌드과정을 거친다.
    • 고급언어를 컴파일하여 모두 기계어(바이너리)로 변환한다
  • 장점
    • 실행 속도가 빠르다
    • 디버깅이 쉽다
  • 단점
    • 실행 전 빌드시간이 필수적으로 소요된다

인터프리터 언어

  • 스크립트 언어라고도 함
  • 실행 전 빌드과정없이 고급언어를 인터프리터가 한줄씩 실시간으로 해석하여 실행한다.
  • 장점
    • 빌드시간이 필요하지 않다.
    • 수정 후 바로 재시작하면 되기에 개발속도가 빠르다
  • 단점
    • 실행 속도가 느리다
    • 코드의 오류 인지 시점이 늦고 디버깅이 힘들다

자바는?

  • 컴파일과 인터프리터 방식을 둘다 사용
    • 소스코드와 기계어 사이의 .class(바이트 코드)가 존재
    • 컴파일 : 소스코드 -> 바이트 코드
    • 인터프리터 : 바이트 코드 -> 바이너리 코드


Garbage Collector

  • 힙 영역에서 참조되지 않는 객체를 주기적으로 검사, 제거하는 메모리 관리 기법
    • 대부분의 객체는 금방 접근 불가능 상태(unreachable)가 된다.
    • 오래된 객체에서 젊은 객체로의 참조는 아주 적게 존재한다.
  • 장점
    • 개발자가 직접 메모리를 관리하지 않아도 됨
    • 메모리 누수 방지
    • 해제된 메모리에 대한 접근 방지
  • 단점
    • 오버헤드가 큼
    • GC 동작 시점을 개발자가 알 수 없음


GC 선별 알고리즘 종류

  • GC Root(root space)로 부터 참조되지 않는 객체를 선별해내는 알고리즘
  • GC Root가 될 수 있는 대상
    • Method 영역의 static 변수
    • Stack 영역의 로컬 변수
    • Native Method Stack 영역의 JNI 레퍼런스

Reference Counting

image

  • root space에서부터 heap 객체의 참조횟수(reference couting)를 기록한다
    • 참조횟수가 0인 객체는 GC 대상이 된다
  • 단 객체끼리 참조하는 순환참조가 일어날 수 있고, 이렇게 되면 해당 객체는 영영 삭제되지 않는다

Mark and Sweep

image

  • Mark
    • 그래프 순회를 통해 사용되는 메모리와 사용되지 않는 메모리를 식별하는 작업
    • Reachability를 계산
  • Sweep
    • Mark 단계에서 사용되지 않음으로 식별된 메모리를 해제하는 작업
    • 여기서 메모리 단편화를 막기 위한 Compaction이 진행됨
  • 장점 : 순환참조 문제를 해결
  • 단점 : 의도적으로 GC를 실행해야하며, 어플리케이션과 GC 실행이 병행됨


Major GC 종류

  • Major GC에서는 Stop the World가 발생한다.
    • 힙 영역의 모든 객체를 검사하기 때문에 시간이 오래걸림
    • Minor GC보다 10배의 시간이 소요되며, 다른 스레드를 중단시킴
    • Stop the World 발생 횟수를 줄이는 것이 GC 튜닝

Serial GC

image

Parallel GC

image

CMS GC

image

G1 GC

image


자바의 GC 동작원리

  • 자바는 Mark and Sweep 방식으로 GC가 동작함

JVM Heap 영역

image

  • Young Generation
    • Eden, Survivor 1,0 영역
    • Minor GC 발생 영역
  • Old Generation
    • Old 영역
    • Java 8 이전엔 Perm 영역이 추가로 존재했음
    • Major GC 발생 영역

전체 동작 과정

  1. 새로 생성된 객체는 Eden 영역에 할당됨
  2. Eden 영역이 꽉차면 Mark & Sweep 실행
    • 살아남은 객체들을 Survivor0 영역으로 이동
    • 이동시키면서 객체의 age bit를 1 증가시킴
  3. 다시 새 객체는 Eden 영역에 할당
  4. Eden 영역이 꽉차면 Eden + Survivor0 영역에서 Mark & Sweep 실행
    • 두 영역에서 살아남은 객체들을 Survivor1 영역으로 이동
  5. 위 과정을 반복하며 살아남는 객체들은 Survivor0, Survivor1 영역에 번갈아가며 이동
  6. 반복 중 Age bit가 threshold값 이상이 되거나 Survivor 영역의 메모리가 부족해지면 객체를 Old 영역으로 이동
    • 이걸 Promotion이라고 함
    • Java 8 - Parallel GC 방식 기준 threshold = 15
  7. Old 영역이 꽉차게 되면 Mark & Sweep (Major GC) 실행


참고링크