[TIL] 코드스쿼드 CS16 9일차
오늘의 활동
- CS04 시작
- 메모리 관리 구현
느낀 점
- 이번 그룹의 마지막 문제이다 보니 난이도가 상이기도 하고, 개념을 정리하는 데만 대부분의 시간을 썼다
- 2주 동안 미션들이 난해하고 구현도 어려웠기 때문에 자바 과정이 시작됐을 때도 이러면 어떡하나라는 생각을 매일 같이 해온 것 같다
- 오늘 슬랙에 호눅스가 남긴 글이 힘든 상황에 굉장히 큰 힘을 주었다
- JK도 항상 말하는 거지만 구현에 집착하지 말고 개념 이해에 집중하는 게 더 중요해 보인다
- 마인드를 바꾸니 2주 동안 지속됐던 미간의 주름이 좀 풀어진 거 같다..
정리
스택 포인터
- JVM에서의 스택 포인터는 스택의 현재 위치를 가리키는 포인터임
- 스택은 메서드 호출 상태를 저장하기 위해 Java 애플리케이션의 각 스레드에서 사용하는 메모리 영역임
- 메서드가 호출되면 새 프레임이 스택에 푸시되고 메서드가 반환되면 프레임이 스택에서 pop 됨
- 스택 포인터는 스택의 현재 위치를 추적하는 데 사용되므로 JVM은 메서드가 호출되고 반환될 때 프레임을 push하고 pop할 위치를 알 수 있음
- 스택 포인터는 JVM에서 다음 정보를 저장하는 데 사용됨
- 지역 변수: 스택의 각 프레임에는 메서드 내에서 선언된 변수인 지역 변수를 위한 공간이 포함됨
- 피연산자 스택: 스택의 각 프레임에는 메서드 실행 중에 중간 결과를 보관하고 작업을 수행하는 데 사용되는 피연산자 스택이 포함됨
- 메서드 반환 주소: 스택의 각 프레임에는 메서드의 반환 주소가 포함되며, 이는 메서드가 반환된 후 실행될 다음 명령을 가리키는 명령 포인터임
- 예외 처리 정보: 스택의 각 프레임에는 예외를 처리하기 위해 JVM에서 사용하는 정보가 포함됨
- 스택 포인터는 또한 JVM이 프로그램 실행의 현재 상태를 추적하고 메서드 호출이 종료될 때 반환 위치를 알 수 있도록 도움
- 스택 크기가 제한되고 고정되어 있다는 점에 유의하는 것이 중요함
- 스택 포인터가 최대 크기에 도달하면 JVM에서 StackOverFlowError가 발생
JVM의 동작 방식
- 자바로 개발된 프로그램을 실행하면 JVM은 OS로 부터 메모리를 할당
- 자바 컴파일러(.javac)가 자바 코스코드(.java)를 자바 바이트코드(.class)로 컴파일
- Class Loader를 통해 JVM Runtime Data Area로 로딩
- Runtime Data Area에 로딩된 .class들은 Execution Engine을 통해 해석
- 해석된 바이트 코드는 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 영역만 존재
- 메모리의 높은 주소 -> 낮은 주소의 방향으로 할당됨
- Heap 영역은 효율적인 GC를 위해 위와 같이 크게 3가지의 영역으로 나뉘게 됨
- Young Generation 영역은 자바 객체가 생성되지마자 저장되고, 생긴 지 얼마 안 되는 객체가 저장되는 공감
- Heap 영역에 객체가 생성되면 최초로 Eden 영역에 할당됨
- 이 영역에 데이터가 어느 정도 쌓이게 되면 참조 정도에 따라 Survivor의 빈 공간으로 이동되거나 회수됨
- Young Generation(Eden + Survivor) 영역이 차게 되면 참조 정도에 따라 Old 영역으로 이동되거나 회수됨
- 이런 Young Generation과 Tenured Generation에서의 GC를 Minor GC라고 함
- Old 영역에 할당된 메모리가 허용치를 넘게 되면, Old 영역에 있는 모든 객체들을 검사하여 참조되지 않는 객체들을 한꺼번에 삭제하는 GC가 실행됨
- 시간이 오래 걸리는 작업이고, 이때 GC를 실행하는 스레드를 제외한 모든 스레드는 작업을 멈춤. 이를 Stop-the-World라고 함
- Stop-the-World가 발생하고 Old 영역의 메모리를 회수하는 GC를 Major GC라고 함
- 위 과정을 가비지 컬렉션 동작 과정에서 자세히 설명
가비지 컬렉션(Garbage Collection)
- 가비지 컬렉션은 영어로 Garbage Collection으로 줄여서 GC라고도 부름
- 자바의 메모리 관리 방법 중 하나로 JVM의 Heap 영역에서 동적으로 할당했던 메모리 영역 중 필요 없게 된 메모리 영역을 주기적으로 삭제하는 프로세스
- 자바는 JVM에 탑재되어 있는 가비지 컬렉터가 메모리 관리를 대행해주기 때문에 개발자 입장에서 메모리 관리, 메모리 누수 문제에서 완벽하게 관리하지 않아도 됨
가비지 컬렉션의 단점
- 개발자가 메모리가 언제 해재되는지 정확하게 알 수 없음
- GC가 동작하는 동안에는 다른 동작을 멈추기 때문에 오버헤드가 발생함
- 이로 인해 GC가 너무 자주 실행되면 소프트웨어 성능 하락의 문제가 되기도 함
가비지 컬렉션의 대상이 되는 객체들
- 객체들은 실질적으로 Heap 영역에서 생성되고, Method 영역이나 Stack 영역 등의 Root 영역에서는 Heap 영역에 생성된 객체의 주소만 참조하는 형식으로 구성됨
- 하지만 이렇게 생성된 Heap 영역의 객체들이 메서드가 끝나는 등의 특정 이벤트들로 인하여 Heap 영역의 메모리 주소를 갖고 있는 참조 변수가 삭제되는 현상이 발생한다면 위 그림의 빨간색 객체와 같이 Heap영역에서 어디서든 참조하고 있지 않은 객체들이 발생하게 됨
- 이러한 객체들을 Unreachable하다고 하며 주기적으로 가비지 컬렉터가 제거해줌
- Reachable: 객체가 참조되고 있는 상태
- Unreachable: 객체가 참조되고 있지 않은 상태 (GC의 대상이 됨)
Mark And Sweep 알고리즘
- Mark And Sweep 알고리즘은 가비지 컬렉션이 동작하는 원리로 루트에서부터 해당 객체에 접근 가능한지에 대한 여부를 메모리 해제의 기준으로 삼음
- Mark 과정: 먼저 Root로부터 그래프 순회를 통해 연결된 객체들을 찾아내어 각각 어떤 객체를 참조하고 있는지 찾아서 마킹함
- Sweep 과정: 참조하고 있지 않은 객체, 즉 Unreachable 객체들을 Heap에서 제거함
- 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라 하고, 이 작업이 너무 잦으면 프로그램 성능에 문제가 될 수 있음
Leave a comment