Java 가비지 수집 메커니즘에 대한 자세한 설명
얼핏 보면 가비지 수집이 수행하는 작업은 이름에서 알 수 있듯이 쓰레기를 찾아서 제거하는 것과 똑같아야 합니다. 실제로는 정반대입니다. 가비지 수집은 아직 사용 중인 모든 개체를 추적하고 나머지 개체를 가비지로 표시합니다. 이를 염두에 두고 "가비지 수집"이라고 하는 자동화된 메모리 재활용이 JVM에서 어떻게 구현되는지 자세히 살펴보겠습니다.
수동 메모리 관리
최신 버전의 가비지 컬렉션을 소개하기 전에 수동으로 메모리를 명시적으로 할당하고 해제해야 했던 시절을 간략하게 살펴보겠습니다. 메모리 해제를 잊어버리면 이 메모리를 재사용할 수 없습니다. 이 메모리는 점유되어 있지만 사용되지는 않습니다. 이 시나리오를 메모리 누수라고 합니다.
다음은 C로 작성된 수동 메모리 관리의 간단한 예입니다.
int send_request() { size_t n = read_size(); int *elements = malloc(n * sizeof(int)); if(read_elements(n, elements) < n) { // elements not freed! return -1; } // … free(elements) return 0; }
보시다시피 Free up을 쉽게 잊어버릴 수 있습니다. 메모리. 메모리 누수는 매우 일반적인 문제였습니다. 지속적으로 코드를 수정해야만 이들과 싸울 수 있습니다. 따라서 인적 오류 가능성을 줄이기 위해 사용되지 않은 메모리를 자동으로 해제하는 보다 우아한 방법이 필요합니다. 이 자동화된 프로세스를 가비지 수집(줄여서 GC)이라고도 합니다.
스마트 포인터
자동 가비지 수집의 초기 구현은 참조 계산입니다. 각 개체가 몇 번 참조되었는지 알 수 있습니다. 카운터가 0에 도달하면 개체를 안전하게 재활용할 수 있습니다. C++의 공유 포인터는 매우 유명한 예입니다.
int send_request() { size_t n = read_size(); stared_ptr<vector<int>> elements = make_shared<vector<int>>(); if(read_elements(n, elements) < n) { return -1; } return 0; }
우리가 사용하는 sharedptr은 이 객체가 참조되는 횟수를 기록합니다. 다른 사람에게 전달하면 개수가 1씩 증가하고 범위를 벗어나면 1씩 감소합니다. 이 개수가 0에 도달하면 sharedptr은 기본 해당 벡터를 자동으로 삭제합니다. 물론 이는 단지 예시일 뿐이고, 일부 독자들이 현실에서는 일어날 가능성이 낮다고 지적하기도 했지만, 실증하기에는 충분합니다.
자동 메모리 관리
위 C++ 코드에서는 메모리 관리를 사용해야 한다고 명시적으로 명시해야 합니다. 그렇다면 모든 객체가 이 메커니즘을 사용한다면 어떻게 될까요? 이는 매우 편리하며 개발자는 메모리 정리에 대해 생각할 필요가 없습니다. 런타임은 더 이상 사용되지 않는 메모리를 자동으로 파악하여 해제합니다. 즉, 자동으로 쓰레기를 재활용합니다. 1세대 가비지 컬렉터는 1959년 Lisp에서 소개되었으며, 그 기술은 오늘날까지 계속해서 발전해 왔습니다.
참조 카운팅
C++의 공유 포인터를 사용하여 방금 시연한 아이디어는 모든 객체에 적용할 수 있습니다. Perl, Python 및 PHP와 같은 많은 언어가 이 방법을 사용합니다. 이는 그림으로 쉽게 설명할 수 있습니다.
녹색 구름은 프로그램에서 여전히 사용되는 개체를 나타냅니다. 기술적인 측면에서 이는 실행 중인 메서드의 지역 변수나 정적 변수와 약간 비슷합니다. 상황은 프로그래밍 언어마다 다를 수 있으므로 이는 우리의 초점이 아닙니다.
파란색 원은 메모리에 있는 객체를 나타내며, 이를 참조하는 객체가 몇 개나 되는지 알 수 있습니다. 회색 원 안의 객체는 더 이상 누구도 참조하지 않습니다. 따라서 이는 가비지 개체이며 가비지 수집기를 통해 정리할 수 있습니다.
멋지죠? 예, 하지만 큰 결함이 있습니다. 일부 고립된 링이 나타나는 것은 쉽습니다. 그 안에 있는 객체는 어떤 도메인에도 속하지 않지만 서로를 참조하므로 참조 번호가 0이 아닙니다. 예는 다음과 같습니다.
보시다시피 빨간색 부분은 실제로 애플리케이션에서 더 이상 사용되지 않는 가비지 개체입니다. 참조 계산의 결함으로 인해 메모리 누수가 발생합니다.
이 문제를 해결하는 방법에는 특별한 "약한" 참조를 사용하거나 특수 알고리즘을 사용하여 순환 참조를 재활용하는 등 여러 가지 방법이 있습니다. 앞서 언급한 Perl, Python, PHP 등의 언어는 모두 유사한 방법을 사용하여 순환 참조를 재활용하지만 이는 이 기사의 범위를 벗어납니다. JVM에서 사용하는 방식을 자세히 소개하겠습니다.
마크 삭제
우선 JVM의 객체 도달성에 대한 정의가 좀 더 명확할 필요가 있습니다. 녹색 구름으로 이전처럼 모호하지는 않지만 가비지 수집 루트 개체(Garbage Collection Roots)에 대한 매우 명확하고 구체적인 정의가 있습니다.
지역 변수
활동 스레드
정적 필드
JNI 참조
기타 (추후 논의 예정)
JVM은 표시 및 삭제 알고리즘을 통해 도달 가능한(생존) 모든 객체를 기록하는 동시에, 도달할 수 없는 객체의 메모리를 보장하여 재사용할 수 있습니다. 이는 두 단계로 구성됩니다.
마킹은 도달 가능한 모든 객체를 순회한 후 해당 객체의 정보를 로컬 메모리에 기록하는 것을 의미합니다.
삭제 연결할 수 없는 개체의 메모리 주소가 다음 메모리 할당에 사용될 수 있는지 확인합니다.
Parallel Scavenge, Parallel Mark+Copy, CMS 등 JVM의 다양한 GC 알고리즘은 모두 이 알고리즘을 다르게 구현한 것이지만 개념적으로는 여전히 동일합니다. 위에서 언급한 두 단계로 이동합니다.
이 구현에서 가장 중요한 점은 더 이상 객체 루프 누출이 없다는 것입니다.
단점은 참조가 유지되는 경우 재활용을 완료하려면 애플리케이션 스레드를 일시 중단해야 한다는 것입니다. 변경하면 셀 수 없습니다. JVM이 해당 작업을 처리할 수 있도록 애플리케이션이 일시 중지되는 상황을 STW(Stop The World 일시 중지)라고도 합니다. 이 일시 중지가 트리거될 가능성은 많지만 가비지 수집이 가장 일반적인 것일 수 있습니다.
읽어주셔서 감사합니다. 도움이 되기를 바랍니다. 이 사이트를 지원해 주셔서 감사합니다!
Java 가비지 수집 메커니즘과 관련된 더 많은 기사를 보려면 PHP 중국어 웹사이트를 주목하세요!