https://dev-dx2d2y-log.tistory.com/163
[Java] JVM 끝까지 파헤치기 독서 #5 - 가비지 컬렉터의 알고리즘 이론
https://dev-dx2d2y-log.tistory.com/138 [CS] JVM 밑바닥까지 파헤치기 독서 #1 - 자바 런타임 메모리 영역C와 C++은 객체 (C에는 객체가 없지만)의 생성, 관리, 삭제까지 모두 관리할 책임을 가지지만, 자바에서
dev-dx2d2y-log.tistory.com
저번에는 가비지컬렉션의 기본적인 알고리즘 이론에 대해서 알아봤다면, 이번에는 핫스팟 알고리즘에 대해 보다 세부적으로 알아보고자한다. 그렇기 때문에 이 게시글은 전적으로 위의 게시글에 의존 중이다.

위 게시글의 최종 요약본인데, 1, 4, 7, 8, 9, 10번만 알면될 듯하다. 자바 내에서는 참조 카운팅 알고리즘도 안쓰고 finalize()는 JDK9부터 Deprecated된 문법이라 굳이 알 필요까지는 없다.
'살아있는 객체' 찾기
https://dev-dx2d2y-log.tistory.com/165
[Java] OopMap으로 참조체인 내 속한 객체 찾기
루트 노드 열거GC를 시작하려할 때 가장 기본이되는 것이 GC루트를 기준으로 '도달 가능성 알고리즘'을 실행시키는 것이다.루트 노드 열거는 도달 가능성 알고리즘을 구현하기 위해서 GC 루트집
dev-dx2d2y-log.tistory.com
저번에는 단지 알고리즘과 이론에 대해서만 알아봤지만, 이번에는 좀 더 실무적인 방법에 대해서 알아보기로한다. 저번에는 단순히 'GC루트로부터 참조되고 있는 객체들은 회수대상이 아니다.'라고만 알아봤지만, 이를 어떻게 구현해야할까?
이는 'OopMap'과 '루트노드열거'라는 과정을 통해 이루어지는데,
가비지컬렉션이 진행될 때는 다른 객체들이 추가되거나, 새로운 참조관계를 맺거나하는 등의 상황이 생기면 안된다. 가비지컬렉션이 진행될 때는 모든 객체들이 일관성을 유지해야하며, 이 일관성을 유지하기 위해서는 적어도 객체의 참조관계를 파악할 때만큼은 모든 스레드가 정지해야한다는 뜻이다.
가비지컬렉션이 시작되기 전, JIT컴파일러가 컴파일을 진행하며 스택의 어느 슬롯의 변수가, 아니면 어느 레지스터에서 참조가 발생하는지 모두 기록한다. 이 기록을 OopMap이라고하는 자료구조에 해당한다.
가비지컬렉션이 시작되면, 가비지컬렉터는 이 OopMap을 통해서 현재 스택 또는 레지스터 등, GC루트가 될 수 있는 곳의 참조를 확인한다. 그 참조에서 호출된 객체와, 그 객체가 호출하고 있는 다른 객체들을 모두 확인한 후에 힙메모리에서 참조되지 않은 객체들을 지워버리는 과정이다.
https://dev-dx2d2y-log.tistory.com/163
[Java] JVM 끝까지 파헤치기 독서 #5 - 가비지 컬렉터의 알고리즘 이론
https://dev-dx2d2y-log.tistory.com/138 [CS] JVM 밑바닥까지 파헤치기 독서 #1 - 자바 런타임 메모리 영역C와 C++은 객체 (C에는 객체가 없지만)의 생성, 관리, 삭제까지 모두 관리할 책임을 가지지만, 자바에서
dev-dx2d2y-log.tistory.com
지워버리는 과정이야 이전에 했으니까 넘어가면 된다.

위에서 말한 내용은 간소화된 버전이고, '살아있는 객체 찾기'에서 연결된 링크로 들어가면 좀 더 자세한 내용이 나온다. 그리고 최종적으로 정리를하자면 바로 위의 과정을 거친다.
가비지컬렉션 타이밍 찾기
https://dev-dx2d2y-log.tistory.com/166
[Java] 가비지컬렉터는 어디에서 실행되어야하는가? - 안전지점(Safe Region)
안전지점https://dev-dx2d2y-log.tistory.com/165 [Java] OopMap으로 참조체인 내 속한 객체 찾기루트 노드 열거GC를 시작하려할 때 가장 기본이되는 것이 GC루트를 기준으로 '도달 가능성 알고리즘'을 실행시키
dev-dx2d2y-log.tistory.com
위에서 루트 노드 열거를 진행했지만, 이 루트 노드 열거는 모든 스레드를 멈추고 나서야 실행할 수 있다. 그래서 이 '멈추는 시점'을 찾아야하는데, 이 시점을 안전지점이라고 칭한다.
안전지점은 스레드의 명령어가 반복문이나 메서드 호출 등, 명령어의 다중화 이전에 스레드의 상태를 명확히 판단할 수 있는 지점으로, 가비지컬렉터가 가비지컬렉션을 수행하려하면 모든 스레드들은 안전지점에 도달한 후 멈추게된다.

안전지점 역시 내용이 길기에 간소화된 부분이 있어서 위의 글을 참고하면 좋다. 위 링크의 글을 요약하자면 위와 같다. 가비지컬렉션이 필요할 때 가비지컬렉션 플래그비트를 true로 설정해두면 스레드들이 안전지점에서 이 플래그비트를 확인하고 멈춘다.
쓰기장벽
이제 실제로 살아있는 객체도 찾았고, 가비지컬렉션을 진행할 타이밍도 찾았다. 어떻게 가비지컬렉션을 진행하는지도 이전에 다뤘고. 이제 중요한 것이 하나 남았는데,

GC루트를 찾을 때 힙메모리 구세대 영역의 전체스캔하는 것을 방지하기 위해 구세대영역에서 일어나는 구세대 - 신세대 간 참조를 '기억집합'이라는 데이터구조에 기록해두고 나중에 GC루트를 찾을 때 그 부분만 찾는 것이다.
이부분을 어떻게 구현하냐면
https://dev-dx2d2y-log.tistory.com/170
[JVM] JVM 내에서 카드테이블과 사전장벽, 사후장벽이란?
기억집합 (카드테이블)https://dev-dx2d2y-log.tistory.com/162 [JVM] GC의 객체회수과정은 어떻게 일어나는가? - 마크-스윕, 마크-카피, 마크-컴팩트https://dev-dx2d2y-log.tistory.com/163 [Java] JVM 끝까지 파헤치기 독
dev-dx2d2y-log.tistory.com

역시 위의 글을 정리해두면 다음과 같이 정리할 수 있는데, 기억집합은 JVM 내에서 '카드테이블'이라는 데이터구조를 통해 구현된다. 카드테이블은 하나의 집합으로, 카드테이블의 각 요소들은 힙메모리를 특정크기의 부분공간에 대응된다. 일반적으로 이 부분공간은 2의 N제곱 바이트의 크기를 가지며, 각각의 부분공간을 카드페이지라고한다.
구세대에서 세대 간 참조가 발생하면 객체가 변수에 대입되는 즉시 해당 카드페이지에 대응되는 카드테이블에 마킹한다. 그리고 GC가 시작되면 마킹된 카드테이블을 기억해뒀다가 GC가 끝난 후 카드테이블을 0으로 밀어버린 후에 GC 이전에 마킹된 카드테이블에 다시 접근해서 여전히 세대 간 참조가 일어나는지 재조사하여 마킹한다.. 요정도?

여기서 현재처럼 사용자스레드와 동시에 동작하는 GC가 등장하면서 "GC가 시작할 때 살아있던 객체는 이번 GC에서는 살려둔다"라는 철학이 대두되었다. 그래서 GC 시작 후 참조해제된 객체들이 어떤 객체인지 기록해두는 방식이 등장했는데, 이 방식을 사전 쓰기장벽이라 칭한다. 사후 쓰기장벽은 이를 기록하지 않는다. 사후 쓰기장벽이 널리 사용되던 시기에는 GC를 진행할 때 모든 스레드를 멈춰두고 GC를 진행했기에 GC 도중에 객체의 참조관계의 변경이 발생하지 않아 필요없던 기술이었다.
표시하기
이제 진짜 다 알 것 같은데?라고 생각할 수 있다.
GC가 시작되면 모든 스레드들이 안전지점에서 멈추고, GC루트들에서 참조가 일어나는 부분이 작성된 OopMap에서 참조체인을 구할 수 있다. 그래서 GC가 시작되는 상황이 도래한다.
이제 마크-스윕이나 마크-카피 등의 알고리즘을 사용해서 메모리만 회수하면 된다. 그런데 "표시"를 어떻게할까?
https://dev-dx2d2y-log.tistory.com/172
[JVM] 객체를 언제, 어떻게 표시할 것인가?
JVM 내에서 도달 가능성 분석 알고리즘을 다시 정의해보자면 https://dev-dx2d2y-log.tistory.com/165 [JVM] OopMap으로 참조체인 내 속한 객체 찾기루트 노드 열거GC를 시작하려할 때 가장 기본이되는 것이 GC루
dev-dx2d2y-log.tistory.com

위와같은 "삼색표시기법"을 사용한다.
표시를 할 때에도 중요한 것이 있는데, 카드테이블에 대해서 다룰 때처럼 현대의 GC는 사용자스레드와 동시에 작동한다. 그래서 아직 확인하지 않은 객체에서 특정 객체의 참조가 해제되고 특정 객체가 이미 확인한 객체와 새로이 참조관계를 맺는다면, 그 특정객체는 GC의 대상이된다. 왜냐하면 앞으로 확인할 객체들을 통해서는 해당 객체에 접근할 수 없고, 그렇다고 이미 확인한 객체를 재확인하자니 시간이 너무 오래걸리기 때문이다.
따라서 삼색표시기법과 함께 두 가지 알고리즘 중 하나를 같이 사용하는데, 위의 상황에서 특정객체를 A, 이미 확인한 객체를 B, 아직 확인하지 않은 객체를 C라고하면,
B를 기록해둔 다음에 스캔이 끝나고 B를 새로이 GC루트로 삼는 방법 (증분 업데이트)
A를 기록해둔 다음에 메모리 회수범위에서 A를 제거하는 방법 (시작단계스냅숏 (SATB))
이 있다. 전자가 사후쓰기장벽, 후자가 사전쓰기장벽에 해당한다. 사전장벽과 사후장벽의 본질적인 차이는 해제되는 객체를 저장하는가이기 때문.
이처럼 저번에는 어떻게 가비지컬렉션을 진행하는지 알아봤다면,
이번에는 가비지컬렉션을 진행하기 전에 살아있는 객체들을 찾는 과정과, 가비지컬렉션을 진행하면서 발생할 수 있는 문제들에 대해서 다뤄보았다.
그래서.. 이렇게 배운 내용들을 실제로 적용해보기 전까지는 이론적인 내용들은 이것으로 마무리할 수 있다. 앞으로 책을 읽어나가면서 중요한 길잡이가 될 것이고, 이 지식들을 다른 곳에 써먹을 수도 있을 것이다. 책에서 벗어난 내용도 많았고, 꽤 어려웠던 내용들도 많았지만 그래도 재미는 있었다. 책 읽는 속도가 너무 느려서 그렇지..
'CS > JVM' 카테고리의 다른 글
| [JVM] 객체를 언제, 어떻게 표시할 것인가? (0) | 2026.01.05 |
|---|---|
| [JVM] JVM 내에서 카드테이블과 사전장벽, 사후장벽이란? (1) | 2026.01.01 |
| [JVM] 가비지컬렉터는 어디에서 실행되어야하는가? - 안전지점(Safe Region) (0) | 2025.12.27 |
| [JVM] OopMap으로 참조체인 내 속한 객체 찾기 (0) | 2025.12.27 |
| [JVM] JVM 끝까지 파헤치기 독서 #5 - 가비지 컬렉터의 알고리즘 이론 (0) | 2025.12.26 |
