본문 바로가기
public void static main/Book

[JVM 밑바닥] 3장 가비지 컬렉터와 메모리 할당 전략 -1

by 햄리뮤 2024. 8. 7.
반응형

조아써 오늘은 3장에 3-1 부터 3-3까지만 정리해보자!

책표지

 

대상이 죽었는가?

자바 세계에서는 거의 모든 객체 인스턴스가 힙에 저장된다. 가비지 컬렉터가 힙을 청소하려면 가장 먼저 어떤 객체가 살아 있고, 또 어떤 객체가 죽었는지 판단해야 한다.

 

도달 가능성 분석 알고리즘

GC루트 라고 하는 루트 객체들을 시작 노드 집합으로 쓰는 것이다. 시작 노드들에서 출발하여 참조하는 다른 객체들로 탐색해 들어간다. 그리고 어떤 객체와 GC 루트 사이를 이어주는 참조 체인이 없다면, 즉 GC 루트로부터 도달 불가능한 객체는 더 이상 사용할 수 없는 게 확실해진다.

객체 5, 객체 6, 객체 7은 GC루트 집합에 도달할 수 없음으로 회수 대상이다.

그럼 자바에서는 어떤 객체가 GC 루트를 이용할 수 있을까?

  • 가상 머신 스택(스택 프레임의 지역 변수 테이블)에서 참조하는 객체: 현재 실행중인 메서드에서 쓰는 매개 변수, 지역 변수, 임시 변수 등.
  • 메서드 영역에서 클래스가 정적 필드로 참조하는 객체: 자바 클래스의 참조 타입 정적 변수.
  • 메서드 영역에서 상수로 참조되는 객체: 문자열 테이블 안의 참조.
  • 네이티브 메서드 스택에서 JNI(네이티브 메서드)가 참조하는 객체.
  • 자바 가상 머신 내부에서 쓰이는 참조: 기본 데이터 타입에 해당하는 Class 객체, (NullPointerException, OutOfMemoryError 등의) 일부 상주 예외 객체, 시스템 클래스 로더.
  • 동기화 락(synchronized 키워드)으로 잠겨 있는 모든 객체.
  • 자바 가상 머신 내부 상황을 반영하는 JMXBean: JVMTI에 등록된 콜백, 로컬 코드 캐시 등.

참조됐다! 참조되지 않았다!

참조 타입 데이터에 저장된 값이 다른 메모리 조각의 시작 주소를 뜻한다면, 이 참조 데이터를 해당 메모리 조각이나 객체를 참조한다고 말한다.

그렇다면, 버리기는 아까운 객체를 표현할 방법은 없을까?

예를들어 메모리가 여유롭다면 그냥 두고, 가비지 컬렉션을 하고 나서도 메모리가 매우 부족하다면 그때 회수하는 객체를 표현하고 싶다면?

  • 강한 참조(Strong reference): 가장 전통적인 정의의 참조를 뜻한다. Object obj = new Object() 처럼 프로그램 코드에서 참조를 할당하는 걸 말한다. 강한 참조 관계가 남아 있는 객체는 가비지 컬렉터가 절대 회수하지 않는다.
  • 부드러운 참조(Soft reference): 유용하지만 필수는 아닌 객체를 표현한다. 부드러운 참조만 남은 객체라면 메모리 오버플로가 나기 직전에 두 번째 회수를 위한 회수 목록에 추가된다. 두 번째 회수 후에도 메모리가 부족하면 그때 메모리 오버플로 예외를 던진다. 
  • 약한 참조(Weak reference): 부드러운 참조와 비슷하지만 연결 강도가 더 약하다. 약한 참조뿐인 객체는 다음번 가비지 컬렉션까지만 살아 있따. 가비지 컬렉터가 동작하기 시작하면 메모리가 넉넉하더라도 약하게 참조된 객체는 모두 회수된다.
  • 유령 참조(Phantom refernce): 참조 중에 가장 약하다('ghost reference' 라고도 한다.) 유령 참조는 객체 수명에 아무런 영향을 주지 않으며, 유령 참조를 통해 객체 인스턴스를 가져오는 것마저 불가능한다. 유령 참조를 거는 유일한 목적은 대상 객체가 회수될 때 알림을 받기 위해서다.

더 이상 쓰이지 않는 클래스 인지 판단은 어떻게 할까?

다음 세 조건을 동시에 만족해야 한다. 조건에 만족하면 쓸모없는 클래스들을 회수하도록 '허용'한다. (반드시 회수하지 않음!)

  • 이 클래스의 인스턴스가 모두 회수 되었다. 즉, 자바 힙에는 해당 클래스와 하위 클래스의 인스턴스가 하나도 존재하지 않는다.
  • 이 클래스를 읽어 들인 클래스 로더가 회수되었다. 이 조건은 OSGI나 JSP 리로딩 처럼 세심하게 설계된 대안 클래스 로더 없이는 충족하기 어렵다.
  • 이 클래스에 해당하는 java.lang.Class 객체를 아무 곳에서도 참조하지 않고, 리플렉션 기능으로 이 클래스의 메서드를 이용하는 곳도 전혀 없다.

가비지 컬렉션 알고리즘

마크-스윕(mark and sweep) 알고리즘

작업을 표시(mark)와 쓸기(sweep)라는 두 단계로 나눠 진행한다. 먼저 회수할 객체들에 모두 표시한 다음, 표시된 객체들을 쓸어 담는 식이다.

또는 반대로 살릴 객체에 표시하고 표시되지 않은 객체를 회수하기도 한다.

하지만 크으으은 단점이 있다!

  1. 실행 효율이 일정하지 않다. 자바 힙이 다량의 객체로 가득 차 있고 그 대부분이 회수 대상이라면 표시하는 일도, 회수하는 일도 모두 커진다. 즉, 객체가 많아질수록 표시하고 쓸어 담는 작업의 효율이 떨어지는 구조다.
  2. 메모리 파편화가 심하다. 가비지 컬렉터가 쓸고 간 자리에는 불연속적인 메모리 파편이 만들어진다. 파편화가 너무 심하면 프로그램이 큰 객체를 만들려 할때 충분한 크기의 연속된 메모리를 찾기가 점점 어려워지고, 그 결과 또 다른 가비지 컬렉션을 유발한다.

마크-카피 알고리즘

가용 메모리를 똑같은 크기의 두 블록으로 나눠 한 번에 한 블록만 사용한다. 한쪽 블록이 꽉 차면 살아남은 객체들만 다른 블록에 복사하고 기존 블록을 한 번에 청소한다.

대다수 객체가 살아남는다면 메모리 복사에 상당한 시간을 허비하는 반면, 대다수가 회수된다면 생존한 소수의 객체만 복사하면 된다.   복사 과정에서 객체들이 메모리의 한쪽 끝에서부터 차곡차곡 쌓이기 때문에 골치 아픈 메모리 파편화 문제로부터 해방 된다.

하지만 메모리를 절반으로 줄여 낭비가 심하다!

 

마크-컴팩트 알고리즘

생존한 모든 객체를 메모리 영역의 한쪽 끝으로 모은 다음, 나머지 공간을 한꺼번에 비운다.

마크 스웝과의 차이는 메모리 이동이 일어난다는 점이다.

생존한 객체를 이동시킨 후, 이동된 객체들을 가리키던 기존 참조들을 모두 갱신하기는 매우 부담될 것이다.

이런 식의 객체 이동은 사용자 애플리케이션을 모두 멈춘 상태에서 진행해야 하므로 신중하게 고려해야 할 단점이다.

 

 

반응형

댓글