관리 메뉴

Rootable의 개발일기

Java 컴파일 과정과 JVM 본문

Java

Java 컴파일 과정과 JVM

dev-rootable 2023. 9. 9. 13:19

📌 JVM(Java Virtual Machine)

 

자바를 실행하기 위한 가상 기계라는 의미로, 자바 프로그램을 실행하는 환경을 만들어 주는 소프트웨어를 말한다.

 

JVM은 자바 실행 환경 JRE(Java Runtime Environment)에 포함되어 있다.

 

출처: https://velog.io/@minseojo/Java-%EC%9E%90%EB%B0%94-%EC%BB%B4%ED%8C%8C%EC%9D%BC-%EA%B3%BC%EC%A0%95-JVM-%EB%82%B4%EB%B6%80-%EA%B5%AC%EC%A1%B0

 

📌 자바 컴파일 과정

 

출처: https://velog.io/@minseojo/Java-%EC%9E%90%EB%B0%94-%EC%BB%B4%ED%8C%8C%EC%9D%BC-%EA%B3%BC%EC%A0%95-JVM-%EB%82%B4%EB%B6%80-%EA%B5%AC%EC%A1%B0

 

  1. 소스 코드 작성 (.java)
  2. 자바 컴파일러(javac.exe)가 자바 소스 코드를 바이트 코드(.class)로 컴파일
    • 바이트 코드는 아직 컴퓨터가 읽을 수 없는 JVM이 읽을 수 있는 코드
  3. 컴파일된 바이트 코드를 JVM의 클래스 로더(Class Loader)에게 전달
  4. 클래스 로더는 동적 로딩을 통해 필요한 클래스들을 로딩 및 링크하여 런타임 데이터 영역(Runtime Data Area의 Method Area), 즉 JVM의 메모리에 올림
  5. 실행 엔진(Execution Engine)은 JVM 메모리에 올라온 바이트 코드들을 명령어 단위로 하나씩 가져와서 실행. 이때 실행 엔진은 두 가지 방식으로 변경함
    1. 인터프리터 : 바이트 코드 명령어를 하나씩 읽어서 해석하고 실행 (단위 속도는 빠르지만, 전체 속도는 느림)
    2. JIT 컴파일러 : 인터프리터의 단점을 보완하기 위해 도입된 방식으로 바이트 코드 전체를 컴파일하여 바이너리 코드로 변경하고, 이를 직접 실행(전체적인 실행 속도는 인터프리터보다 빠름)

 

🔎 클래스 로더(Class Loader)

 

클래스 로더는 .class 파일들을 묶어서 JVM이 OS로부터 할당받은 메모리 영역인 Runtime Data Area에 적재한다.

 

 

 

클래스 로더는 동적 로딩을 가능하게 한다.

 

✔ 동적 로딩의 장단점

 

동적 로딩은 필요할 때마다 해당 기능을 메모리에 불러와 사용하는 방법이다.

 

컴파일 타임에 모든 클래스의 정보를 불러올 필요가 없으므로 런타임 전까지 메모리가 낭비되는 것을 방지한다.

 

런타임 에러를 실행 시점에 가서야 알 수 있으며, 메모리에 미리 로딩되어 있지 않아 동적 로딩에 시간이 필요하기 때문에 성능 저하로 이어질 수 있다. 이러한 단점 때문에 정적 로딩처럼 바로 메모리에 올리는 static을 사용하는 것이다.

 

💡 정적 로딩

프로그램을 실행할 때, 모든 실행 파일을 메모리에 로딩하는 방법

그래서 모든 기능이 메모리에 있어 바로 사용하기 좋지만, 메모리를 많이 차지한다는 단점이 있다.

 

🔎 런타임 데이터 영역(Runtime Data Area)

 

JVM의 메모리 영역으로 자바 애플리케이션을 실행할 때 사용되는 데이터들을 적재하는 영역

 

크게 모든 스레드가 공유하는 영역과 스레드마다 독립적으로 사용하는 영역으로 나뉜다.

 

출처 :https://8iggy.tistory.com/229

 

✔ 메서드(클래스, Static) 영역

 

  • 가장 먼저 데이터가 저장되는 영역
  • 클래스 로더에 의해 로드된 클래스, 메서드 정보와 클래스 변수 정보 저장
  • 프로그램 시작 ~ 종료까지 메모리에 적재
  • 명시적 NULL 선언 시 GC(Garbage Collector)가 청소
  • 모든 스레드가 공유하는 영역

 

✔ 힙(Heap) 영역

 

  • 런타임 시 결정되는 참조 자료형이 저장되는 영역
  • new 연산자를 통해 생성된 객체(인스턴스)와 컬렉션이 저장되는 공간
    • 메서드 영역에 로드된 클래스만 생성 가능
  • 객체가 더 이상 사용하지 않거나 명시적 NULL 선언 시 GC가 청소
    • 자동으로 해제되지 않기 때문에 주기적으로 GC가 제거하는 영역
  • thread-safe 하지 않기 때문에 동시성에 대한 고려가 필요
  • 모든 스레드가 공유하는 영역

 

💡 Thread Safe

멀티 스레드 프로그래밍(ex.java)에서 자원에 대해 여러 스레드로부터 접근이 이루어져도 여러 기법을 통해 올바른 실행을 보장하는 것을 말한다.

다시 말해서 한 스레드가 하나의 함수를 사용할 때, 다른 스레드가 그 함수에 접근하여 동시에 실행되더라도 각 스레드에서의 함수 수행 결과가 올바르게 나오도록 하는 것을 의미한다.

Reference:
https://gompangs.tistory.com/entry/OS-Thread-Safe%EB%9E%80
 

[OS] Thread Safe란?

Thead Safe 스레드 안전(thread 安全, 영어: thread safety)은 멀티 스레드 프로그래밍에서 일반적으로 어떤 함수나 변수, 혹은 객체가 여러 스레드로부터 동시에 접근이 이루어져도 프로그램의 실행에

gompangs.tistory.com

 

 

✔ Stack 영역

 

  • 컴파일 시 결정되는 기본 자료형(실제값), Wrapper class 등이 저장됨
  • 힙 영역의 객체를 가리키는 참조 변수(주소값)를 저장
  • 지역 변수, 파라미터, 리턴 값, 임시 값 등 저장
  • 메서드가 호출될 때마다 각각의 Stack 프레임이 생성됨
    • 각 Stack 프레임은 하나의 메서드에 대한 정보를 저장
  • {} 혹은 메서드가 종료될 때 삭제됨
    • 메서드 종료 시 프레임 별로 삭제됨
  • 각 스레드 별로 생성되는 영역

 

✔ PC 레지스터

 

  • JVM이 수행할 명령어의 주소를 저장하는 영역
    • OS의 PC 레지스터와 비슷한 역할이지만 CPU와 별개로 JVM이 관리함
  • 스레드가 시작될 때마다 생성됨
  • 각 스레드 별로 생성되는 영역

 

✔ 네이티브 메서드 스택

 

  • 바이트 코드가 아닌 기계어로 작성된 코드를 실행하는 영역
  • 다른 언어로 작성된 코드를 수행하기 위함
  • JNI(Java Native Interface)를 통해 바이트 코드로 변환됨
  • JNI 호출 시 생성됨
  • Java 코드를 수행하다 JNI 호출 시 Java Stack에서 Native Stack으로 동적 연결을 통해 확장됨
    • 따라서 Stack에서 연결할 수 있음
  • 각 스레드 별로 생성되는 영역

 

📌 Runtime Data Area 세부 구분

 

출처 :https://8iggy.tistory.com/229

 

🔎 힙 영역의 세부 구분

 

Young Generation은 생성된 지 얼마 안 된 객체가 저장되는 공간이다. Heap 영역에 객체가 생성되면 가장 먼저 Eden 영역에 할당된다. 이 영역에 데이터가 일정 이상 쌓이게 되면 Survivor의 빈 공간으로 이동되거나 GC에 의해 회수된다.

 

Young Generation 전체 영역이 일정 이상 쌓이게 되면 참조 정도에 따라 Old 영역으로 이동되거나 GC에 의해 회수된다. 이렇게 Young Generation과 Tenured Generation에서의 GC를 'Minor GC'라고 한다.

 

Old 영역에 할당된 메모리가 허용치를 넘게 되면, Old 영역에 있는 모든 객체들을 검사하여 참조되지 않는 객체들을 한꺼번에 제거하는 GC가 실행된다. 해당 작업은 시간이 오래 걸리고, 이를 수행하는 스레드 외 모든 스레드는 작업을 멈추게 된다. 이를 'stop-the-world'라 한다. 이렇게 'stop-the-world'가 발생하고 Old 영역의 메모리를 회수하는 GC를 'Major GC'라고 한다.

 

Method 영역은 JVM 벤더마다 다양한 형태로 구현할 수 있으며, 오라클 핫스팟 JVM 기준으로 Permanent Area 혹은 Permanent Generation(PermGen)을 Method 영역이라고 정의한다.

 

PermGen은 Java 8 이전에는 Heap에 포함되어 있었다. 그런데 PermGen은 GC의 대상이 되지 않는 말그대로 영구적인 공간이었다. 또한, JVM에 의해 관리되었기 때문에 메모리가 작았고 탄력적으로 조절되지 않아 OOM 에러의 원인이 되었다.

 

💡 OOM(Out of Memory) 에러

설정된 메모리보다 요청된 메모리가 많아서 JVM에서 발생하는 에러

 

🔎 PermGen에서 Metaspaces로 대체

 

이처럼 PermGen 영역은 JVM에 의해 크기가 제한된 영역으로 메모리 범위 초과 문제가 있었다.

 

그래서 Java 8부터는 이를 제거하고 Metaspaces로 대체되었다. 따라서 일부 데이터는 Heap에 편입되고 일부는 JVM에 의해 메모리가 제한되지 않는 Native Memory(Off-Heap) 영역으로 전환된 것이다. Native Memory는 OS에 의해 메모리 할당 공간이 자동으로 조절되므로 이론상 아키텍처가 지원하는 메모리 크기까지 확장할 수 있다.

 

static 객체 및 상수화된 String 객체를 Heap 영역으로 옮겨 GC의 대상이 될 수 있도록 수정하고, ClassLoader가 로드한 클래스들의 metadata는 Metaspaces에 저장하도록 했다.

 

✍ 클래스의 metadata

 - class structure
 - Method metadata (클래스 파일 내의 method_info, bytecode, exception table, contants 등)
 - constant pool
 - Annotations

 

Static 객체는 참조가 살아있는 한 GC에 의해 회수되지 않는다. 반대로 참조를 잃은 Static 객체는 GC의 대상이 된다. 이것은 Heap 영역으로 옮겨진 Static 객체가 Heap 영역의 특징에 맞게 GC의 대상이 될 수 있도록 한 것이다.

 

📝 정리

 

  • Java 8부터는 PermGen이 존재하지 않는다.
  • PermGen에 저장하던 데이터 중 일부를 Heap과 Metaspaces로 편입했다.
  • Metaspaces는 Native Memory로 OS에 의해 관리되므로 용량 조절이 가능하다.

 

References:

https://velog.io/@mincho920/Java%EC%9D%98-%EB%8F%99%EC%A0%81%EB%A1%9C%EB%94%A9

 

https://ubange.tistory.com/223

 

https://sdesigner.tistory.com/91

 

https://8iggy.tistory.com/229

 

https://jaemunbro.medium.com/java-metaspace%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90-ac363816d35e

 

https://frhyme.github.io/java/java_memory_stack_heap_memory/

'Java' 카테고리의 다른 글

try-with-resources  (0) 2023.09.13
자바 리플렉션 API  (0) 2023.09.12
자바언어의 특징  (0) 2023.09.09
객체 지향 프로그래밍(Object Oriented Programming)  (0) 2023.05.01
배열과 리스트  (0) 2023.04.26