가지지 콜렉션(Garbage Collection)
메모리 관리 시스템에서 살펴 볼 세 가지 측면
- 최초의 메모리 할당
- 라이브 데이터의 식별
- 이전에 할당 되었지만, 현재는 죽은 객체가 점유한 메모리의 회수
자동해제와 명시적 해제의 중요한 차이점
- 한꺼번에 모든 공간을 해제
- 할당할 때, 가비지 컬렉팅에 이용해야 하는 정보를 좀 더 추가해서 할당 한다.
- 프로그램 작성 스타일이 달라진다. 좀 더 잦은 힙 할당을 사용한다.
할당(Allocation)
할당전략
- 순차 할당
- 한쪽 끝에서 부터 할당하는 전략
- 프리리스트 할당
- 할당 가능한 공간에 대한 자료구조를 별도로 관리하는 전략
- 자바에서 둘 사이의 성능 차이는 전체 실행 시간의 1% 정도
- 캐시 지역성에서는 순차할당이 우세(이동식 컬렉터)
- 단편화가 발생한 경우는 프리리스트 우세(비이동식 컬렉터)
순차할당
sequentialAllocation(n) :
result <- free
newFree <- result + n
if newFree > limit
return null
free <- newFree
return result
프리리스트 할당
firstFitAllocate(n):
prev <- addressOf(head)
loop
curr <- next(prev)
if curr = null
return null
else if size(curr) < n
prev <- curr
else
return listAllocate(prev,curr,n)
헤드 포인터(head pointer)에서 부터 셀을 탐색, null 이면 메모리 고갈, 현재 포인터의 셀의 크기가 n 보다 작은 경우는 다음 포인터로 listAllocate에서 실제로 할당 해줌
할당하고자 하는 셀의 크기가 n보다 큰 경우에는 셀을 분할해서 할당 할 수 있게 해주고, 분할된 셀의 남은 공간은 다시 프리리스트로 반환한다.
listAllocate(prev,curr,n):
result <- curr
if shouldSplit(size(curr),n)
remainder <- result+n
next(remainder) <- next(curr)
size(remainder) <- size(curr) - n
next(prev) <- remainder
else
next(prev) <- next(curr)
return result
프리리스트 할당 종류
- 최초적합할당
- 루프 돌면서 공간할당요청을 충족할 수 있는 발견된 최초의 셀에 할당
- 셀 분할 후 남게 되는 작은 셀은 리스트의 앞쪽에 쌓이게 된다.
- 점점 할당을 느리게 하는 요인
- 차선적합할당
- 리스트에서 마지막 탐색이 성공한 지점부터 적당한 크기의 셀을 찾기 시작
- 리스트 끝에 도달하면 처음 부터 다시 시작
- 리스트의 앞부분에 존재하는 작은 크기의 셀을 반복적으로 탐색하지 않아도 된다.
- 최적적합할당
- 요청 크기와 가장 일치하는 세을 찾는다.
- 공간 낭비는 적지만, 할당으로 인한 성능이 떨어진다.
단편화(Fragmentation)
- 프로그램을 실행하고, 할당과 해제가 반복됨에 따라 많은 수의 프리 셀을 양산
- 부정적 영향
- 실제로 요청을 만족시킬 만한 충분한 양의 프리메모리가 존재하지만 프리 셀 중에 이 요청을 만족시킬 만큼의 크기를 가진 셀이 존재 하지 않을 수 있음
- 명시적 해제 프로그램은 보통 강제 종료
- 자동 해제 프로그램은 GC발생
- 이상적인 최적의 할당
- 할당과 해제 요청이 성공적으로 이뤄질 수 있는 전체 순서를 정하고 이에 필요한 가장 작은 공간을 사용한다.
- NP-hard 문제이다.
단편화의 Worst case
힙분할(Partitioning the heap)
- 모든 객체가 동일한 컬렉션 알고리즘에 의해 관리될 필요는 없다.
- 컬렉터가 상이한 범주의 객체들을 이동시키기를 원하는지, 원한다면 어떻게 이동시킬 것인지에 따라 다르게 관리할 수 있음
분할이유
- 이동성에 의한 분할
- 이동 가능한 객체와 그렇지 않은 객체를 구분(예를들어 커널로 전달되어 I/O가 수행되는..)
- 크기에 의한 분할
- 큰 객체는 옮기지 않는 것이 바람직
- 공간에 의한 분할
- 빠른 할당과 우수한 공간지역성을 보유한 전략으로 관리하는 공간에 객체를 생성하는 것이 바람직
- 수명이 길고 단편화가 급하지 않으면 비이동식 컬렉터가 주로 관리
- 할당비율이 상대적으로 높고 수명이 얼마남지 않은 객체는 빠른 할당과 저렴한 수집비용을 위해 복사컬렉터의 관리하에 있는 공간
- 효율성을 위한 분할
- 대부분의 객체는 젊어서 죽는다.
정지시간 단축을 위한 분할
- 복사 컬렉터의 수집 비용은 라이브 객체의 양에만 좌우
- 마크 스윕의 경우에는 수집 비용이 주로 스윕 비용
- 컬렉터가 추적하는 수집 예정 공간의 크기를 제한함으로써 수집 또는 마킹된 객체의 양을 제한해 결과적으로 가비지 컬렉션에 필요한 시간을 제한
지역성을 위한 분할
- 메모리 계층이 갈수록 복잡해짐에 따라 지역성의 중요성이 커짐
- 예)부모 객체와 자식 객체를 함께 배치
- 여유공간을 반환할 가능성이 높은 힙의 부분집합에 노력을 집중
스레드에 의한 분할
- 자신의 스레드에서 할당 했지만, 다른 스레드가 접근할 수 없게 된 객체만을 수집한다면 스레드간 동기화 비용을 줄일 수 있음
가변성에 의한 분할
- 최근에 생성된 객체는 예전에 생성된 오래된 객체보다 더 자주 변경되는 경향이 있음.
- 참조 카운팅 기반 메모리 관리자는 갱신 때 마다 높은 오버헤드를 유발하는 경향이 있어서, 변경 빈도가 많은 객체에는 부적합
- 매우 큰 힙에서는 비교적 적은 비율의 객체만이 특정 기간 내에 변경되지만, 추적 컬렉터는 GC대상이 되는 항상 모든 객체를 방문
가장 유명한 분할 기법
- 객체를 나이에 따라 구분하고 좀 더 젊은 객체를 우선적으로 수집하는 세대 컬렉션(Generational collection)