JProfiler 도움말Download

메모리 누수 찾기


일반적인 메모리 사용과 메모리 누수를 구분하는 것은 종종 간단하지 않습니다. 그러나 과도한 메모리 사용과 메모리 누수는 동일한 증상을 가지므로 동일한 방식으로 분석할 수 있습니다. 분석은 두 단계로 진행됩니다: 의심스러운 객체를 찾고, 왜 그 객체들이 여전히 힙에 있는지 알아내는 것입니다.

새로운 객체 찾기

메모리 누수가 있는 애플리케이션이 실행 중일 때, 시간이 지남에 따라 더 많은 메모리를 소비합니다. 메모리 사용량의 증가를 감지하는 가장 좋은 방법은 VM 텔레메트리와 "모든 객체" 및 "녹화된 객체" 뷰의 차이 기능을 사용하는 것입니다. 이러한 뷰를 통해 문제가 있는지, 그리고 그 심각성을 판단할 수 있습니다. 때로는 호출 히스토그램 테이블의 차이 열이 이미 문제에 대한 아이디어를 제공합니다.

메모리 누수에 대한 더 깊은 분석은 힙 워커의 기능을 필요로 합니다. 특정 사용 사례 주위의 메모리 누수를 자세히 조사하려면 "힙 표시" 기능이 가장 적합합니다. 이를 통해 특정 이전 시점 이후 힙에 남아 있는 새로운 객체를 식별할 수 있습니다. 이러한 객체에 대해서는 여전히 힙에 정당하게 있는지, 아니면 객체가 더 이상 목적을 제공하지 않음에도 불구하고 잘못된 참조가 그것들을 활성 상태로 유지하는지 확인해야 합니다.

관심 있는 객체 집합을 격리하는 또 다른 방법은 할당 녹화를 통해서입니다. 힙 스냅샷을 찍을 때, 모든 녹화된 객체를 표시하는 옵션이 있습니다. 그러나 할당 녹화를 특정 사용 사례에만 제한하고 싶지 않을 수도 있습니다. 또한, 할당 녹화는 높은 오버헤드를 가지므로 힙 표시 작업은 상대적으로 훨씬 작은 영향을 미칠 것입니다. 마지막으로, 힙 워커는 힙을 표시한 경우 새로 사용오래된 사용 하이퍼링크를 사용하여 선택 단계에서 오래된 객체와 새로운 객체를 선택할 수 있게 해줍니다.

가장 큰 객체 분석

메모리 누수가 사용 가능한 힙을 채우면, 프로파일된 애플리케이션의 다른 유형의 메모리 사용을 압도할 것입니다. 이 경우, 새로운 객체를 조사할 필요가 없으며, 단순히 가장 중요한 객체를 분석하면 됩니다.

메모리 누수는 매우 느린 속도를 가질 수 있으며 오랫동안 지배적이지 않을 수 있습니다. 메모리 누수가 눈에 띄게 될 때까지 프로파일링하는 것은 실용적이지 않을 수 있습니다. OutOfMemoryError가 발생할 때 자동으로 HPROF 스냅샷을 저장하는 JVM의 내장 기능을 사용하면, 메모리 누수가 일반적인 메모리 소비보다 더 중요한 스냅샷을 얻을 수 있습니다. 사실, 항상 추가하는 것이 좋습니다.

-XX:+HeapDumpOnOutOfMemoryError

VM 매개변수나 프로덕션 시스템에 추가하여 개발 환경에서 재현하기 어려운 메모리 누수를 분석할 수 있는 방법을 제공합니다.

메모리 누수가 지배적이라면, 힙 워커의 "가장 큰 객체" 뷰의 상위 객체는 실수로 유지된 메모리를 포함할 것입니다. 가장 큰 객체 자체는 정당한 객체일 수 있지만, 그들의 지배자 트리를 열면 누수된 객체로 이어질 것입니다. 간단한 상황에서는 힙의 대부분을 포함하는 단일 객체가 있을 것입니다. 예를 들어, 맵이 객체를 캐시하는 데 사용되고 그 캐시가 결코 지워지지 않는다면, 그 맵은 가장 큰 객체의 지배자 트리에 나타날 것입니다.

가비지 컬렉터 루트에서 강한 참조 체인 찾기

객체는 강하게 참조될 때만 문제가 될 수 있습니다. "강하게 참조된다"는 것은 가비지 컬렉터 루트에서 객체로의 참조 체인이 적어도 하나 있다는 것을 의미합니다. "가비지 컬렉터" 루트(줄여서 GC 루트)는 가비지 컬렉터가 알고 있는 JVM의 특별한 참조입니다.

GC 루트에서 참조 체인을 찾으려면, "들어오는 참조" 뷰나 힙 워커 그래프에서 GC 루트로의 경로 표시 작업을 사용할 수 있습니다. 이러한 참조 체인은 실제로 매우 길 수 있으므로 일반적으로 "들어오는 참조" 뷰에서 더 쉽게 해석할 수 있습니다. 참조는 아래에서 상위 수준의 객체로 향합니다. 검색 결과인 참조 체인만 확장되며, 동일한 수준의 다른 참조는 노드를 닫고 다시 열거나 컨텍스트 메뉴에서 모든 들어오는 참조 표시 작업을 호출할 때까지 보이지 않습니다.

참조 노드에서 사용된 GC 루트 유형 및 기타 용어에 대한 설명을 얻으려면 트리 범례를 사용하십시오.

트리에서 노드를 선택하면 비모달 트리 범례가 선택된 노드에서 사용된 모든 아이콘과 용어를 강조 표시합니다. 대화 상자의 행을 클릭하면 하단에 설명이 표시됩니다.

중요한 유형의 가비지 컬렉터 루트는 스택에서의 참조, JNI를 통한 네이티브 코드에 의해 생성된 참조, 현재 사용 중인 라이브 스레드 및 객체 모니터와 같은 리소스입니다. 또한, JVM은 중요한 시스템을 유지하기 위해 몇 가지 "끈적한" 참조를 추가합니다.

클래스와 클래스로더는 특별한 순환 참조 체계를 가지고 있습니다. 클래스는 클래스로더와 함께 가비지 컬렉션됩니다.

  • 해당 클래스로더에 의해 로드된 클래스 중 어떤 것도 라이브 인스턴스를 가지고 있지 않을 때
  • 클래스로더 자체가 클래스에 의해서만 참조되지 않을 때
  • java.lang.Class 객체가 클래스로더의 컨텍스트 외부에서 참조되지 않을 때

대부분의 경우, 클래스는 관심 있는 GC 루트로의 경로에서 마지막 단계입니다. 클래스 자체는 GC 루트가 아닙니다. 그러나 사용자 정의 클래스로더가 사용되지 않는 모든 상황에서, 그것들을 GC 루트로 취급하는 것이 적절합니다. 이는 가비지 컬렉터 루트를 검색할 때 JProfiler의 기본 모드이지만, 루트 옵션 대화 상자에서 이를 변경할 수 있습니다.

GC 루트로의 최단 경로를 해석하는 데 문제가 있는 경우, 추가 경로를 검색할 수 있습니다. 일반적으로 모든 경로를 GC 루트로 검색하는 것은 권장되지 않습니다. 왜냐하면 많은 경로를 생성할 수 있기 때문입니다.

라이브 메모리 뷰와는 달리, 힙 워커는 참조되지 않은 객체를 절대 표시하지 않습니다. 그러나 힙 워커는 강하게 참조된 객체만 표시하지 않을 수도 있습니다. 기본적으로 힙 워커는 약한 참조, 팬텀 참조 또는 파이널라이저 참조에 의해서만 참조된 객체를 제거하지만, 소프트 참조에 의해서만 참조된 객체는 유지합니다. 소프트 참조는 힙이 소진되지 않는 한 가비지 컬렉션되지 않으므로, 큰 힙 사용량을 설명할 수 없을 수 있기 때문에 포함됩니다. 힙 스냅샷을 찍을 때 표시되는 옵션 대화 상자에서 이 동작을 조정할 수 있습니다.

힙 워커에 약하게 참조된 객체가 있는 것은 디버깅 목적으로 흥미로울 수 있습니다. 나중에 약하게 참조된 객체를 제거하려면 "약한 참조에 의해 유지된 객체 제거" 검사를 사용할 수 있습니다.

GC 루트로의 경로를 검색할 때, 힙 워커 옵션 대화 상자에서 객체를 유지하기 위해 선택된 참조 유형이 고려됩니다. 그렇게 함으로써, GC 루트 검색 경로는 항상 객체가 힙 워커에 유지된 이유를 설명할 수 있습니다. GC 루트 검색 경로의 옵션 대화 상자에서 허용 가능한 참조 유형을 모든 약한 참조로 확장할 수 있습니다.

전체 객체 집합 제거

지금까지 우리는 단일 객체만 살펴보았습니다. 종종 메모리 누수의 일부인 동일한 유형의 많은 객체가 있을 것입니다. 많은 경우, 단일 객체의 분석은 현재 객체 집합의 다른 객체에도 유효할 것입니다. 관심 있는 객체가 다양한 방식으로 참조되는 일반적인 경우에는 "병합된 지배 참조" 뷰가 현재 객체 집합을 힙에 유지하는 참조를 찾는 데 도움이 될 것입니다.

지배 참조 트리의 각 노드는 해당 참조를 제거하면 현재 객체 집합에서 가비지 컬렉션에 적합한 객체 수를 알려줍니다. 여러 가비지 컬렉터 루트에 의해 참조된 객체는 지배적인 들어오는 참조를 가질 수 없으므로 뷰는 객체의 일부에만 도움이 되거나 비어 있을 수도 있습니다. 이 경우, 병합된 들어오는 참조 뷰를 사용하고 가비지 컬렉터 루트를 하나씩 제거해야 합니다.