Published:
Updated:

스택 포인터

  • JVM에서의 스택 포인터는 스택의 현재 위치를 가리키는 포인터임
  • 스택은 메서드 호출 상태를 저장하기 위해 Java 애플리케이션의 각 스레드에서 사용하는 메모리 영역임
  • 메서드가 호출되면 새 프레임이 스택에 푸시되고 메서드가 반환되면 프레임이 스택에서 pop 됨
  • 스택 포인터는 스택의 현재 위치를 추적하는 데 사용되므로 JVM은 메서드가 호출되고 반환될 때 프레임을 push하고 pop할 위치를 알 수 있음
  • 스택 포인터는 JVM에서 다음 정보를 저장하는 데 사용됨
    • 지역 변수: 스택의 각 프레임에는 메서드 내에서 선언된 변수인 지역 변수를 위한 공간이 포함됨
    • 피연산자 스택: 스택의 각 프레임에는 메서드 실행 중에 중간 결과를 보관하고 작업을 수행하는 데 사용되는 피연산자 스택이 포함됨
    • 메서드 반환 주소: 스택의 각 프레임에는 메서드의 반환 주소가 포함되며, 이는 메서드가 반환된 후 실행될 다음 명령을 가리키는 명령 포인터임
    • 예외 처리 정보: 스택의 각 프레임에는 예외를 처리하기 위해 JVM에서 사용하는 정보가 포함됨
  • 스택 포인터는 또한 JVM이 프로그램 실행의 현재 상태를 추적하고 메서드 호출이 종료될 때 반환 위치를 알 수 있도록 도움
  • 스택 크기가 제한되고 고정되어 있다는 점에 유의하는 것이 중요함
  • 스택 포인터가 최대 크기에 도달하면 JVM에서 StackOverFlowError가 발생

JVM의 동작 방식

  1. 자바로 개발된 프로그램을 실행하면 JVM은 OS로 부터 메모리를 할당
  2. 자바 컴파일러(.javac)가 자바 코스코드(.java)를 자바 바이트코드(.class)로 컴파일
  3. Class Loader를 통해 JVM Runtime Data Area로 로딩
  4. Runtime Data Area에 로딩된 .class들은 Execution Engine을 통해 해석
  5. 해석된 바이트 코드는 Runtime Data Area의 각 영역에 배치되어 수행하며, 이 과정에서 Execution Engine에 의해 GC의 작동과 스레드 동기화가 이루어짐

JVM 메모리 구조

  • 위 사진은 JDK 11 이후를 기반으로 함
  • JVM 프로세스가 사용할 수 있는 메모리이고, 운영 체제(OS)에 의해 할당됨

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

  • 런타임 데이터 영역은 JVM의 메모리 영역으로, 자바 애플리케이션을 실행할 때 사용되는 데이터들을 적재하는 영역
  • 모든 스레드가 공유해서 사용 (GC의 대상)
    • Heap 영역
    • Method 영역
  • 스레드마다 하나씩 생성
    • Stack 영역
    • PC 레지스터
    • 네이티브 메서드 스택

Stack 영역

  • 메서드 내에서 정의하는 기본 자료형에 해당되는 지역변수의 데이터 값이 저장되는 공간
  • 메서드가 호출될 때 스택 영역에 스택 프레임이 생기고 그 안에 메소드를 호출
  • primitive 타입의 데이터(int, double, byte, long, boolean etc.) 에 해당되는 지역변수, 매개변수 데이터 값이 저장
  • 메서드가 호출될 때 메모리에 할당되고 종료되면 메모리에서 사라짐
  • Stack은 후입선출 LIFO(Last-In-First-Out)의 특성을 가지며, Scope의 범위를 벗어나면 스택 메모리에서 사라짐
  • 메모리의 높은 주소 -> 낮은 주소의 방향으로 할당됨
스택 프레임(stack frame)
  • 하나의 메서드에 필요한 메모리 덩어리를 묶어서 스택 프레임이라고 부름
  • 하나의 메서드당 하나의 스택 프레임이 필요하며, 메서드를 호출하기 직전 스택 프레임을 Stack에 생성한 후 메서드를 호출함
  • 스택 프레임에 쌓이는 데이터는 메서드의 매개변수, 지역변수, 리턴값 등이 있음
  • 만약 메서드 호출 범위가 종료되면 스택에서 제거됨

Heap 영역

  • JVM이 관리하는 프로그램 상에서 데이터를 저장하기 위해 런타임 시 동적으로 할당하여 사용하는 영역
  • 참조형 데이터 타입을 갖는 인스턴스, 배열 등이 저장되는 공간
  • 단, Heap 영역에 있는 오브젝트들을 가리키는 레퍼런스 변수는 stack에 적재
  • Heap 영역은 Stack 영역과는 다르게 보관되는 메모리가 호출이 끝나더라도 삭제되지 않고 유지됨. 그러다 어떤 참조 변수도 Heap 영역에 있는 인스턴스를 참조하지 않게 된다면, GC에 의해 메모리에서 청소됨
  • Stack은 스레드 개수마다 각각 생성되지만, Heap은 몇 개의 스레드가 존재하든 상관없이 단 하나의 Heap 영역만 존재
  • 메모리의 높은 주소 -> 낮은 주소의 방향으로 할당됨


  1. Heap 영역은 효율적인 GC를 위해 위와 같이 크게 3가지의 영역으로 나뉘게 됨
  2. Young Generation 영역은 자바 객체가 생성되지마자 저장되고, 생긴 지 얼마 안 되는 객체가 저장되는 공감
  3. Heap 영역에 객체가 생성되면 최초로 Eden 영역에 할당됨
  4. 이 영역에 데이터가 어느 정도 쌓이게 되면 참조 정도에 따라 Survivor의 빈 공간으로 이동되거나 회수됨
  5. Young Generation(Eden + Survivor) 영역이 차게 되면 참조 정도에 따라 Old 영역으로 이동되거나 회수됨
  6. 이런 Young Generation과 Tenured Generation에서의 GC를 Minor GC라고 함
  7. Old 영역에 할당된 메모리가 허용치를 넘게 되면, Old 영역에 있는 모든 객체들을 검사하여 참조되지 않는 객체들을 한꺼번에 삭제하는 GC가 실행됨
  8. 시간이 오래 걸리는 작업이고, 이때 GC를 실행하는 스레드를 제외한 모든 스레드는 작업을 멈춤. 이를 Stop-the-World라고 함
  9. Stop-the-World가 발생하고 Old 영역의 메모리를 회수하는 GC를 Major GC라고 함

Reference

가비지 컬렉션(Garbage Collection)

  • 가비지 컬렉션은 영어로 Garbage Collection으로 줄여서 GC라고도 부름
  • 자바의 메모리 관리 방법 중 하나로 JVM의 Heap 영역에서 동적으로 할당했던 메모리 영역 중 필요 없게 된 메모리 영역을 주기적으로 삭제하는 프로세스
  • 자바는 JVM에 탑재되어 있는 가비지 컬렉터가 메모리 관리를 대행해주기 때문에 개발자 입장에서 메모리 관리, 메모리 누수 문제에서 완벽하게 관리하지 않아도 됨

가비지 컬렉션의 단점

  1. 개발자가 메모리가 언제 해제되는지 정확하게 알 수 없음
  2. GC가 동작하는 동안에는 다른 동작을 멈추기 때문에 오버헤드가 발생함
  3. 이로 인해 GC가 너무 자주 실행되면 소프트웨어 성능 하락의 문제가 되기도 함

가비지 컬렉션의 대상이 되는 객체들

  • 객체들은 실질적으로 Heap 영역에서 생성되고, Method 영역이나 Stack 영역 등의 Root 영역에서는 Heap 영역에 생성된 객체의 주소만 참조하는 형식으로 구성됨
  • 하지만 이렇게 생성된 Heap 영역의 객체들이 메서드가 끝나는 등의 특정 이벤트들로 인하여 Heap 영역의 메모리 주소를 갖고 있는 참조 변수가 삭제되는 현상이 발생한다면 위 그림의 빨간색 객체와 같이 Heap영역에서 어디서든 참조하고 있지 않은 객체들이 발생하게 됨
  • 이러한 객체들을 Unreachable하다고 하며 주기적으로 가비지 컬렉터가 제거해줌
    • Reachable: 객체가 참조되고 있는 상태
    • Unreachable: 객체가 참조되고 있지 않은 상태 (GC의 대상이 됨)

Mark And Sweep 알고리즘

  • Mark And Sweep 알고리즘은 가비지 컬렉션이 동작하는 원리로 루트에서부터 해당 객체에 접근 가능한지에 대한 여부를 메모리 해제의 기준으로 삼음
  1. Mark 과정: 먼저 Root로부터 그래프 순회를 통해 연결된 객체들을 찾아내어 각각 어떤 객체를 참조하고 있는지 찾아서 마킹함
  2. Sweep 과정: 참조하고 있지 않은 객체, 즉 Unreachable 객체들을 Heap에서 제거함
  3. Compact 과정: Sweep 후에 분산된 객체들을 Heap의 시작 주소로 모아 메모리가 할당된 부분과 그렇지 않은 부분으로 압축함 (가비지 컬렉터 종류에 따라 하지 않는 경우도 존재)

가비지 컬렉션 동작 과정

첫 번째 과정

  • 객체가 처음 생성되고 Heap 영역의 Eden에 age-bit 0으로 할당됨
  • 이 age-bit는 Minor GC에서 살아남을 때마다 1씩 증가함
두 번째 과정

  • 시간이 지나 Heap 영역의 Eden 영역에 객체가 다 쌓이게 되면 Minor GC가 한 번 일어나게 됨
  • 참조 정도에 따라 Survival 0 영역으로 이동하거나 회수됨
세 번째 과정

  • Eden 영역에는 계속해서 신규 객체들이 생성됨
  • Eden 영역에 객체가 다 쌓이게 되면 Young Generation(Eden + Survivor) 영역에 있는 객체들을 비어있는 Survival인 Survival 1 영역에 이동
  • 살아남은 모든 객체들의 age가 1씩 증가
네 번째 과정

  • Eden 영역에 신규 객체들도 가득 차게 되면 다시 한 번 minor GC가 일어남
  • Young Generation 영역에 있는 객체들을 비어 있는 Survival인 Survival 0으로 이동시킴
  • 살아남은 모든 객체들의 age가 1씩 증가
  • 이 과정을 계속 반복
다섯 번째 과정

  • 네 번째 과정을 계속 반복하다 보면 age bit가 특정 숫자 이상으로 되는 경우가 발생
  • 이때 JVM에서 설정해놓은 age bit에 도달하게 되면 오랫동안 쓰일 객체라 판단 후 Old generation 영역으로 이동시킴
  • 이 과정을 Promotion이라고 함
마지막 과정

  • 시간이 지나 Old 영역에 할당된 메모리가 허용치를 넘게 되면, Old 영역에 있는 모든 객체들을 검사하여 참조되지 않는 객체들을 한꺼번에 삭제하는 GC가 실행됨
  • 이렇게 Old generation 영역의 메모리를 회수하는 GC를 Major GC라고 함
  • Major GC는 시간이 오래 걸리는 작업이고, 이때 GC를 실행하는 스레드를 제외한 모든 스레드는 작업을 멈추게 됨
  • 이를 Stop-the-World라 하고, 이 작업이 너무 잦으면 프로그램 성능에 문제가 될 수 있음

Reference

Tags:

Categories:

Published:
Updated:

Leave a comment