[Java] 자바의 메모리 관리(번역)
https://www.dynatrace.com/resources/ebooks/javabook/how-garbage-collection-works/
자바의 메모리 관리는 내장된 가비지 컬렉션으로 처리되는데요. 이 언어의 가장 훌륭한 업적 중 하나라 할 수 있겠습니다. 이건 개발자들이 메모리에 대한 할당과 제거를 신경쓰지 않고도 새 객체를 만들수 있게 해줍니다. 가비지컬렉션이 메모리의 재사용을 위해 알아서 정리해주니까요.
이건 더 적은 보일러플레이트 코드로 더 빠른 개발을 가능하게 해주죠. 메모리 누수와 다른 메모리 관련 문제들을 제거해서요. (적어도 이론상으로는)
#역주
보일러 플레이트: 재사용 가능한. 여러곳에서 필수적으로 사용되는 코드
아이러니하게도, 너무 많은 객체를 생성하고 제거해도 자바 가비지컬렉션은 매우 잘 작동하는 것처럼 보이는데요.
대부분의 메모리 운영 이슈가 해결이 되긴 합니다.
그런데 종종 생성 비용에 심각한 성능 문제가 보이기도 합니다.
모든 상황에 적합한 가비지컬렉션은 복잡하고 최적화가 어려운 시스템이 되었거든요...
가비지 컬렉션에 대해 머리를 싸매기 위해서는, 먼저 자바 가상머신에서 메모리 운영이 어떻게 동작하는지를 이해해야합니다.
가비지컬렉션은 정말 어떻게 동작하나요?
많은 사람들이 가비지컬렉션(이하 GC)이 죽은 객체들을 수집해서 제거한다고 생각하는데요.
사실, 자바의 GC는 이와는 반대로 동작합니다!
살아있는 객체들은 계속 추적되고 나머지 것들이 쓰레기로 지정됩니다. 보다시피, 이러한 근본적 오해는 많은 성능 문제로 이어질 수 있습니다.
힙에 대해서 한번 살펴볼까요? 힙은 동적으로 할당돼서 사용되는 메모리 영역입니다.
대부분의 구성에서는 운영체제가 JVM이 프로그램을 실행할 동안 운영할 양만큼 힙을 할당해주는데요. 이건 두가지의 중요한 영향을 야기합니다.
첫째, 모든 단독 객체가 운영체제와의 전역 동기화를 필요로 하진 않으니까, 객체 생성이 더 빨라질 수 있습니다.
할당은 메모리 배열의 일부를 요구하고 오프셋 포인터를 앞으로 이동시킵니다. 그림 2.1에서 보이듯이요.
그다음 할당은 이 오프셋에서 시작해서 그 배열의 다음부분을 또 요구합니다.
둘째. 객체가 더이상 쓰이지 않을때, 가비지컬렉터는 메모리를 회수해서 미래의 객체 할당에 재사용할수 있도록 합니다.
이건 명시적인 삭제가 없고, 메모리를 운영체제로 돌려주지 않는다는걸 뜻합니다.

그림2.1: 새로운 객체들이 사용된 힙의 끝부분에서부터 할당된다.
모든 객체들은 JVM이 관리하는 힙 영역에 할당됩니다.
개발자가 사용하는 모든 아이템은 이런 방식으로 처리되죠. 클래스 객체나, 정적변수나 기타등등 이런저런 코드 요소들이요.
객체가 참조되고있는 동안은, JVM은 그게 살아있다고 생각합니다.
As long as an object is being referenced, the JVM considers it alive.
객체가 더이상 참조되지 않고 응용 프로그램의 코드에 의해 도달할수 없게 된다면, 가비지컬렉터는 그걸 제거해서 재사용 가능하 메모리로 만듭니다.
되게 단순하게 들릴 수도 있는데요. 이런 질문을 해볼수도 있겠습니다. "트리에서 첫번째 참조는 무엇인가?"
Garbage-Collection Roots—The Source of All Object Trees
모든 객체 트리는 하나 이상의 루트 객체들을 가집니다. 어플리케이션이 그 루트에 도달할 수 있는 동안은, 그 tree는 도달할 수 있다고 봅니다.
하지만 그 루트 객체들은 언제 도달가능하다고 판단되는걸까요?
가비지컬렉션 루트라고 불리는, 언제나 도달 가능한 특별한 객체들이 있습니다. 자세한건 그림 2.2를 보시고요...
여튼 자체 루트에 가비지컬렉션 루트를 가지는 객체도 있습니다.
자바에서 GC 루트는 4가지 종류가 있습니다.
1.지역변수
지역변수들은 스레드 내 스택에 의해 생명을 유지하는데요. 이건 진짜 객체에 대한 가상 참조가 아니라 표시는 하지 않습니다.
모든 의도와 목적을 위해, 지역변수들은 GC 루트입니다.
->For all intents and purposes, local variables are GC roots.
2.액티브 자바 스레드
액티브 자바 스레드들은 항상 살아있는 객체로 간주되는 GC 루트입니다. 이건 실상 스레드-로컬 변수를 위한 기능이죠
3.정적변수
정적변수들은 그 클래스에 의해 참조됩니다. 이게 GC 루트가 되는 이유죠.
클래스 자체는 가비지컬렉션될 수 있는데요. 이건 참조되는 모든 정적변수를 제거합니다.
이건 어플리케이션 서버나 OSGi containers, 클래스 로더를 사용할때 특별히 중요합니다.
이와 관련된 문제들은 Problem Patterns 섹션에서 논의합니다.
4.JNI 레퍼런스
JNI 레퍼런스는 네이티브 코드가 JNI 호출의 일부로 생성한 자바 객체입니다.
이 객체들은 JVM이 네이티브 코드에서의 참조 여부를 알지 못하기 때문에 특별히 생성하는건데요.
이런 객체들은 매우 특별한 형태의 GC 루트로 구성되닌데요. 자세한 설명은 아래의 Problem Patterns 섹션에서 다룰겁니다.
역주
JNI: 자바 네이티브 인터페이스. 자바에서 네이티브 프로그램을 돌릴수 있도록 해주는 기능

그림 2.2: GC 루트들은 JVM에서 참조하는 객체이므로, 다른 객체들이 가비지컬렉트되지 않게끔 유지한다.
간단한 자바 어플리케이션은 다음과 같은 GC 루트들을 가집니다.
1.메인 메서드 내의 지역변수들
2.메인스레드
3.메인 클래스 내의 정적변수들
마크앤스윕으로 가비지 정리하기
객체가 더이상 쓰이지 않는지를 확정하기 위해서, JVM은 간헐적으로 마크앤스윕 알고리즘을 호출합니다.
뭐 그렇게 복잡한건 아니에요. 다음과 같이 2단계 프로세스를 가집니다.
1.GC 루트부터 시작해서 모든 객체 참조를 탐색하고, 객체를 찾을때마다 살아있다고 표시(mark)합니다.
2.객체로 마킹되지 못한 힙 메모리는 전부 할당이 해제됩니다. free로 표시를 해놓고, 사용되지 않는 객체로 sweep한거죠.
여튼 그래서 가비지 컬렉션은 고전적인 메모리 누수를 예방할 수 있습니다. 도달이 불가능하지만 삭제되지 않는 객체들이요.
그런데, 이건 '고전적인' 메모리 누수에 대해서만 작동합니다. 그냥 개발자가 참조를 깜빡해서 도달이 여전히 가능한 미사용객체는 여전히 갖고있을거란 거죠. 이런 객체들은 가비지컬렉트되지 않아요.
더 나쁜 소식은, 논리적 메모리 누수는 소프트웨어에 의해서 탐지될 수 없다는겁니다. (그림2.3을 참고해보세요.)
가장 좋은 분석 소프트웨어도 의심스러운 객체만 강조표시해주는것에 그칩니다.
아래의 메모리 사용률 검토 및 가비지컬렉션 섹션에서 메모리 누수 분석을 조사해볼겁니다.
https://www.dynatrace.com/resources/ebooks/javabook/impact-of-garbage-collection-on-performance/

그림 2.3: 객체들은 GC 루트에서 직간접적으로 참조가 불가능해지면 지워진다. 여기에 고전적인 메모리 누수는 없다.
분석은? 진짜 메모리 누수를 확인하진 못한다. 의심스러운 것만 지적할 뿐이다.