JVM의 객체 생성에서 메모리 관리를 알아보자

크리링·2025년 5월 3일
1

JVM

목록 보기
1/2
post-thumbnail

일을 하면서 JVM은 구체적으로 메모리 관리를 어떻게 할까?, JAVA의 GC는 어떻게 정리 대상 메모리인걸 판단할까? 에 대한 내용이 궁금했는데 찾아 보다가 적합한 도서로 보이는 JVM 밑바닥까지 파헤치기라는 책을 발견했고, 읽었고 내가 이해한 부분의 내용을 정리해봐야겠다.




예시를 통한 JVM 객체 생성

class Student {
  String name = "크리링";
  
  public Student(){}
  
  public void updateName(String name){
    this.name = name;
  }
}

Student student = new Student();

이 코드에서 JVM은 어떻게 동작할까?

클래스 로딩 단계

  1. 클래스 로딩시 Student 클래스 정보 메서드 영역에 저장

    • Student 클래스 구조, 메서드, 필드 정보 저장
  2. 크리링 문자열 String Constant Pool에 저장, name의 필드의 초기값으로 사용


객체 생성 단계

  1. 클래스 로딩 여부 확인

  2. 클래스 메타정보를 기반으로 필요한 힙 메모리 크기 계산

  3. 힙에 메모리 할당 (TLAB 사용 등)

  4. 0으로 초기화

  5. 객체 헤더 설정 (class pointer, hash code, GC 정보 등)

  6. 인스턴스 필드 초기화 (name은 크리링을 가리키게 됨)

  7. 생성자 호출


자바 가상 머신 스택 처리

  1. 자바 가상머신 스택은 생성된 힙 객체 참조






JVM의 메모리 영역에 대해서 알아보자

메모리

JVM은 자바 프로그램을 실행하는 동안 필요한 메모리를 몇 개의 데이터 영역으로 나눠 관리한다. 이 영역들은 각각 목적과 생성/삭제 시점이 있다.


(이미지 참고 : [Java] 런타임 데이터 영역(Runtime Data Area)에 대해)

  • 스레드 별 데이터 영역
    • 프로그램 카운터
    • 자바 가상 머신 스택
    • 네이티브 메서드 스택
  • 모든 스레드 공유 영역
    • 메서드






프로그램 카운터

현재 실행중인바이트코드 줄 번호 표시기

  • 자바 가상 머신에서의 멀티스레딩은 CPU 코어를 여러 스레드가 교대로 사용하는 방식으로 구현되기 때문에 특정 시각에 각 코어는 한 스레드의 명령어만 실행

  • 스레드 전환 후 이전에 실행하다 멈춘 지점을 정확하게 복원하려면 스레드 가각에는 고유한 프로그램 카운터 필요

  • 각 스레드의 카운터는 서로 영향을 주지 않는 독립된 영역에서 저장



자바 가상 머신 스택

자바 메서드를 실행하는 스레드의 메모리 모델을 설명한다.
(연결된 스레드와 운명을 같이 한다.)

  • 각 메서드가 호출될 때마다 자바 가상 머신은 스택 프레임을 만들어 지역 변수 테이블, 피연산자 스택, 동적 링크, 메서드 반환값 등의 정보 저장

  • 그런 후 스택 프레임을 가상 머신 스택에 푸시하고, 메서드 끝나면 팝하는 일을 반복

  • 데이터 공간은 컴파일 시에 할당

  • 가상 머신을 어떻게 구현하느냐에 따라 슬롯 하나가 32비트, 64비트일 수 있다.



네이티브 메서드 스택

JVM 가상 머신 스택과 비슷한 역할

  • JVM 가상 머신 스택은 자바 메서드를 실행할 때 사용, 네이티브 메서드 스택은 네이티브 메서드를 실행할 때 사용



자바 애플리케이션이 사용할 수 있는 가장 큰 메모리

  • 모든 스레드가 공유하며 가상 머신이 구동될 때 만들어진다.

  • 객체 인스턴스 저장이 목적, 거의 모든 객체 인스턴스가 이 영역에 할당

  • GC 관리하는 메모리 영역 (GC 힙이라고 불리기도 함)

  • 객체 할당 효율을 높이고자 스레드 로컬 할당 버퍼 여러개로 나뉜다.



메서드

모든 스레드가 공유
가상 머신이 읽어 들인 타입 정보, 상수, 정적 변수, JIT 컴파일러가 컴파일한 코드 캐시 등을 저장






Error

public static void main(String[] args) {
    List<Class> list = new ArrayList<>();
    while(true) {
      list.add(new Class());
    }
  }

  static class Class {
    private int x;

    public Class() {
    }
  }

  • 힙의 최대 용량을 넘어섬

필요없는 객체가 원인이라면 메모리 누수



JVM 가상 머신 영역

public class Main {

  public static void main(String[] args) {
    try {
      Test2.test();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  static class Test2 {

    public static void test() {
      long unused1, unused2, unused3, unused4, unused5, unused6, unused7, unused8, unused9, unused10, unused11, unused12, unused13, unused14, unused15, unused16, unused17, unused18, unused19, unused20, unused21, unused22, unused23, unused24, unused25, unused26, unused27, unused28, unused29, unused30, unused31, unused32;
      test();
    }
  }

  • 스택 프레임 에 계속된 변수 할당으로 스택 프레임 메모리 초과






응용

String 변경

  • 자바 가상 머신 스택에 더 큰 용량이 할당되야 한다면?
class Student {
  String name;
  
  public Student(String name){
    this.name = name
  }
  
  public void updateName(String name){
    this.name = name;
  }
}

Student student = new Student("크리링");
student.updateName("크리링크리링");



클래스 로딩 단계

  1. 클래스 로딩시 Student 클래스 정보 메서드 영역에 저장

    • Student 클래스 구조, 메서드, 필드 정보 저장
  2. 크리링 문자열 String Constant Pool에 저장, name의 필드의 초기값으로 사용


객체 생성 단계

  1. 클래스 로딩 여부 확인

  2. 클래스 메타정보를 기반으로 필요한 힙 메모리 크기 계산

  3. 힙에 메모리 할당 (TLAB 사용 등)

  4. 0으로 초기화

  5. 객체 헤더 설정 (class pointer, hash code, GC 정보 등)

  6. 인스턴스 필드 초기화 (name은 크리링을 가리키게 됨)

  7. 생성자 호출


자바 가상 머신 스택 처리

  1. 자바 가상머신 스택은 생성된 힙 객체 참조

메서드 호출 및 문자열 변경 처리

  1. 크리링크리링 String Constant Pool 확인 및 생성

  2. 메서드 호출 및 name 참조 변경

  • 새로운 스택 프레임 생성 (name 파라미터에 크리링크리링 전달)
  • this.name = name 실행
  • 메서드 종료시 해당 프레임 pop



List 변경

  • 자바 가상 머신 스택에 필드를 리스트로 가지고 있는데 더 큰 리스트를 할당한다면?
class Student {
  List<Integer> scores = List.of(100, 90, 80);
  
  public Student(){}
  
  public void updateScores(List<Integer> scores){
    this.scores = scores;
  }
}

Student student = new Student();
student.updateScores(List.of(100, 90, 80, 70, 60, 50, 40, 30, 20, 10));



클래스 로딩 단계

  1. 클래스 로딩시 Student 클래스 정보 메서드 영역에 저장
    • Student 클래스 구조, 메서드, 필드 정보 저장

객체 생성 단계

  1. 클래스 로딩 여부 확인

  2. 클래스 메타정보를 기반으로 필요한 힙 메모리 크기 계산

  3. 힙에 메모리 할당 (TLAB 사용 등)

  4. 0으로 초기화

  5. 객체 헤더 설정 (class pointer, hash code, GC 정보 등)

  6. 인스턴스 필드 초기화 (scores = List.of(100, 90, 80) 실행됨)

    • List.of(...) 호출로 불변 리스트 객체 생성 → 힙에 저장
    • scores 필드는 이 리스트의 참조값을 저장
  7. 생성자 호출


힙에 리스트 생성

  1. 힙에 List.of(100, 90, 80) 생성 후 scores는 참조 값 저장

자바 가상 머신 스택 처리

  1. 자바 가상머신 스택은 생성된 힙 객체 참조

메서드 호출

  1. 메서드 호출 및 List 참조 변경
    • List.of(100, 90, 80, 70, 60, 50, 40, 30, 20, 10 힙 영역에 생성
    • this.scores = scores 실행으로 참조값 변경




응용 부분의updateNameupdateScores 예시에서 더는 참조하지 않는크리링 값과 List.of(100, 90, 80) 값은 GC로 제거될까?
정답은 크리링은 아니다List.of(100, 90, 80)은 맞다이다.
이유는 String메서드 영역의 String Constant Pool에서 관리하여 GC 대상이 아니고
리스트힙 영역에서 관리하기 때문에 GC 대상이 되기 때문이다.
(다만 "크리링"도 new String("크리링")처럼 만들면 GC 대상이 될 수 있다)

그렇다면 GC는 어떻게 동작할까?
다음 글에...

0개의 댓글