메모리 프로파일링
힙에 있는 객체에 대한 정보를 얻는 방법은 두 가지가 있습니다. 한편으로는 프로파일링 에이전트가 각 객체의 할당 및 가비지 수집을 추적할 수 있습니다. JProfiler에서는 이를 "할당 녹화"라고 합니다. 이는 객체가 어디에서 할당되었는지를 알려주며, 임시 객체에 대한 통계를 생성하는 데에도 사용할 수 있습니다. 다른 한편으로는 JVM의 프로파일링 인터페이스가 프로파일링 에이전트가 모든 활성 객체와 그 참조를 함께 검사하기 위해 "힙 스냅샷"을 찍을 수 있게 합니다. 이 정보는 객체가 왜 가비지 수집되지 않는지를 이해하는 데 필요합니다.
할당 녹화와 힙 스냅샷 모두 비용이 많이 드는 작업입니다. 할당 녹화는 런타임 특성에 큰 영향을 미치는데, 이는 java.lang.Object
생성자를 계측해야 하고 가비지
수집기가 프로파일링 인터페이스에 지속적으로 보고해야 하기 때문입니다. 이 때문에 할당은 기본적으로 녹화되지 않으며, 녹화를 시작하고 중지해야
합니다. 힙 스냅샷을 찍는 것은 일회성 작업입니다. 그러나 이는 JVM을 몇 초 동안 멈출 수 있으며, 획득한 데이터를 분석하는 데 상대적으로 오랜 시간이 걸릴 수 있으며, 이는 힙의 크기에 따라
달라집니다.
JProfiler는 메모리 분석을 두 개의 뷰 섹션으로 나눕니다: "라이브 메모리" 섹션은 주기적으로 업데이트될 수 있는 데이터를 제공하며, "힙 워커" 섹션은 정적 힙 스냅샷을 보여줍니다. 할당 녹화는 "라이브 메모리" 섹션에서 제어되지만, 녹화된 데이터는 힙 워커에서도 표시됩니다.
메모리 프로파일링으로 해결할 수 있는 세 가지 가장 일반적인 문제는 다음과 같습니다: 메모리 누수 찾기, 메모리 소비 줄이기 및 임시 객체 생성 줄이기. 첫 번째 두 문제의 경우, 주로 힙 워커를 사용하여 JVM에서 가장 큰 객체를 누가 보유하고 있는지와 그것들이 어디에서 생성되었는지를 주로 살펴봅니다. 마지막 문제의 경우, 이미 가비지 수집된 객체를 포함하기 때문에 녹화된 할당을 보여주는 라이브 뷰에만 의존할 수 있습니다.
인스턴스 수 추적
힙에 어떤 객체가 있는지 개요를 얻으려면, "모든 객체" 뷰가 모든 클래스와 그 인스턴스 수의 히스토그램을 보여줍니다. 이 뷰에 표시되는 데이터는 할당 녹화로 수집되는 것이 아니라 인스턴스 수만 계산하는 미니 힙 스냅샷을 수행하여 수집됩니다. 힙이 클수록 이 작업을 수행하는 데 시간이 더 오래 걸리므로, 이 뷰는 현재 값으로 자동 업데이트되지 않습니다.
메모리 누수를 찾을 때, 종종 인스턴스 수를 시간에 따라 비교하고 싶습니다. 모든 클래스에 대해 그렇게 하려면, 뷰의 차이 기능을 사용할 수 있습니다. 모든 객체의 두 덤프가 동시에 선택되면, 차이 열이 삽입되고 인스턴스 수의 히스토그램은 마킹 시점의 기준선 값을 녹색으로 표시합니다. 모든 객체의 새 덤프를 찍을 때, 가장 오래된 선택된 덤프는 선택된 상태로 남아 있으며, 모든 객체의 새 덤프와의 차이가 표시됩니다.
덤프 선택기는 덤프가 찍힌 시간 스탬프를 보여줍니다. 덤프를 더블 클릭하여 더 쉽게 식별할 수 있도록 레이블을 추가할 수 있습니다. 모든 객체의 덤프는 트리거 액션 또는 Controller API로도 트리거할 수 있으며, 여기서도 레이블을 지정할 수 있습니다.
반면, "녹화된 객체" 뷰는 할당 녹화를 시작한 후에 할당된 객체의 인스턴스 수만 보여줍니다. 할당 녹화를 중지하면 새로운 할당은 추가되지 않지만, 가비지 수집은 계속 추적됩니다. 이렇게 하면 특정 사용 사례에 대해 힙에 남아 있는 객체를 볼 수 있습니다. 객체가 오랫동안 가비지 수집되지 않을 수 있음을 유의하십시오. GC 실행 도구 모음 버튼을 사용하여 이 프로세스를 가속화할 수 있습니다. 대부분의 동적으로 업데이트되는 뷰와 마찬가지로, 동결 도구 모음 버튼이 표시된 데이터를 업데이트하는 것을 중지하기 위해 제공됩니다.
현재 마크 도구 모음 버튼을 사용하여 "녹화된 객체" 뷰에서도 선택된 기준선에 대한 차이 열을 표시할 수 있습니다. 선택된 클래스에 대해, 컨텍스트 메뉴에서 선택 항목을 클래스 트래커에 추가 액션을 사용하여 시간에 따른 그래프를 표시할 수도 있습니다.
할당 지점
할당 녹화가 활성화되면, JProfiler는 객체가 할당될 때마다 호출 스택을 기록합니다. 스택-워킹 API와 같은 정확한 호출 스택을 사용하지 않는데, 이는 비용이 너무 많이 들기 때문입니다. 대신, CPU 프로파일링에 대해 구성된 것과 동일한 메커니즘이 사용됩니다. 이는 호출 스택이 호출 트리 필터에 따라 필터링되며, 실제 할당 지점이 호출 스택에 존재하지 않는 메서드에 있을 수 있음을 의미합니다. 이는 무시되거나 압축-필터링된 클래스에서 비롯된 것입니다. 그러나 이러한 변경 사항은 직관적으로 이해하기 쉽습니다: 압축-필터링된 메서드는 압축-필터링된 클래스에 대한 추가 호출에서 수행되는 모든 할당에 대해 책임이 있습니다.
샘플링을 사용하는 경우, 할당 지점이 대략적이 되어 혼란스러울 수 있습니다. 시간 측정과 달리, 특정 클래스가 어디에서 할당될 수 있고 어디에서 할당될 수 없는지에 대한 명확한 아이디어가 있는 경우가
많습니다. 샘플링은 통계적 그림을 그리므로, java.util.HashMap.get
이 자신의 클래스를 할당하는 것과 같은 불가능해 보이는 할당 지점을 볼 수 있습니다. 정확한
숫자와 호출 스택이 중요한 분석의 경우, 계측과 함께 할당 녹화를 사용하는 것이 좋습니다.
CPU 프로파일링과 마찬가지로, 할당 호출 스택은 호출 트리로 표시되며, 호출 수와 시간이 아닌 할당 수와 할당된 메모리로 표시됩니다. CPU 호출 트리와 달리, 할당 호출 트리는 자동으로 표시 및 업데이트되지 않는데, 이는 트리 계산이 더 비용이 많이 들기 때문입니다. JProfiler는 모든 객체뿐만 아니라 선택된 클래스나 패키지에 대해서도 할당 트리를 보여줄 수 있습니다. 다른 옵션과 함께, 이는 현재 데이터에서 할당 트리를 계산하도록 JProfiler에 요청한 후 표시되는 옵션 대화 상자에서 구성됩니다.
CPU 호출 트리의 유용한 속성은 각 노드가 자식 노드에서 소비된 시간을 포함하기 때문에 위에서 아래로 누적된 시간을 따라갈 수 있다는 것입니다. 기본적으로 할당 트리는 동일한 방식으로 작동하며, 각 노드는 자식 노드에 의해 수행된 할당을 포함합니다. 할당이 호출 트리 깊숙한 곳의 리프 노드에서만 수행되더라도, 숫자는 위로 전파됩니다. 이 방식으로, 할당 호출 트리의 가지를 열 때 조사할 가치가 있는 경로를 항상 볼 수 있습니다. "자체 할당"은 실제로 노드에 의해 수행된 것이며, 자손에 의해 수행된 것이 아닙니다. CPU 호출 트리와 마찬가지로, 퍼센티지 막대는 다른 색상으로 표시됩니다.
할당 호출 트리에서는 특히 선택된 클래스에 대한 할당을 표시할 때 할당이 수행되지 않는 많은 노드가 자주 있습니다. 이러한 노드는 실제 할당이 이루어진 노드로 이어지는 호출 스택을 보여주기 위해 존재합니다. 이러한 노드는 JProfiler에서 "브리지" 노드라고 하며, 위의 스크린샷에서 볼 수 있듯이 회색 아이콘으로 표시됩니다. 어떤 경우에는 할당의 누적이 방해가 될 수 있으며, 실제 할당 지점만 보고 싶을 수 있습니다. 할당 트리의 뷰 설정 대화 상자는 그 목적을 위해 누적되지 않은 숫자를 표시하는 옵션을 제공합니다. 활성화되면, 브리지 노드는 항상 0 할당을 표시하고 퍼센티지 막대가 없습니다.
할당 핫스팟 뷰는 할당 호출 트리와 함께 채워지며, 선택된 클래스 생성을 담당하는 메서드에 직접 집중할 수 있게 해줍니다. 녹화된 객체 뷰와 마찬가지로, 할당 핫스팟 뷰는 현재 상태를 마킹하고 시간에 따른 차이를 관찰하는 것을 지원합니다. 차이 열이 뷰에 추가되어 현재 값 마크 액션이 호출된 시점 이후 핫스팟이 얼마나 변경되었는지를 보여줍니다. 할당 뷰는 기본적으로 주기적으로 업데이트되지 않으므로, 계산 도구 모음 버튼을 클릭하여 기준선 값과 비교할 새 데이터 세트를 얻어야 합니다. 자동 업데이트는 옵션 대화 상자에서 사용할 수 있지만, 큰 힙 크기에는 권장되지 않습니다.
할당 녹화 비율
모든 할당을 녹화하는 것은 상당한 오버헤드를 추가합니다. 많은 경우, 할당의 총 숫자는 중요하지 않으며, 상대적인 숫자가 문제를 해결하는 데 충분합니다. 그래서 JProfiler는 기본적으로 10번째 할당만 녹화합니다. 이는 모든 할당을 녹화하는 것에 비해 오버헤드를 대략 1/10로 줄입니다. 모든 할당을 녹화하거나, 목적에 따라 더 적은 할당이 충분한 경우, 녹화된 객체 뷰와 할당 호출 트리 및 핫스팟 뷰의 매개변수 대화 상자에서 녹화 비율을 변경할 수 있습니다.
이 설정은 세션 설정 대화 상자의 "고급 설정->메모리 프로파일링" 단계에서도 찾을 수 있으며, 오프라인 프로파일링 세션에 대해 조정할 수 있습니다.
할당 녹화 비율은 "녹화된 객체" 및 "녹화된 처리량"에 대한 VM 텔레메트리에 영향을 미치며, 해당 값은 구성된 비율로 측정됩니다. 스냅샷을 비교할 때, 첫 번째 스냅샷의 할당 비율이 보고되며, 필요한 경우 다른 스냅샷은 이에 따라 조정됩니다.
할당된 클래스 분석
할당 트리 및 할당 핫스팟 뷰를 계산할 때, 미리 보고 싶은 클래스나 패키지를 지정해야 합니다. 이는 이미 특정 클래스에 집중한 경우 잘 작동하지만, 사전 개념 없이 할당 핫스팟을 찾으려고 할 때는 불편합니다. 한 가지 방법은 "녹화된 객체" 뷰를 보고 선택된 클래스나 패키지에 대한 할당 트리 또는 할당 핫스팟 뷰로 전환하기 위한 컨텍스트 메뉴의 액션을 사용하는 것입니다.
또 다른 방법은 모든 클래스에 대한 할당 트리 또는 할당 핫스팟으로 시작하고, 클래스 표시 액션을 사용하여 선택된 할당 지점 또는 할당 핫스팟에 대한 클래스를 표시하는 것입니다.
할당된 클래스의 히스토그램은 호출 트리 분석으로 표시됩니다. 이 액션은 다른 호출 트리 분석에서도 작동합니다.
클래스 분석 뷰는 정적이며, 할당 트리 및 핫스팟 뷰가 재계산될 때 업데이트되지 않습니다. 분석 다시 로드 액션은 먼저 할당 트리를 업데이트한 다음 새 데이터에서 현재 분석 뷰를 재계산합니다.
가비지 수집된 객체 분석
할당 녹화는 활성 객체를 보여줄 뿐만 아니라, 가비지 수집된 객체에 대한 정보도 유지합니다. 이는 임시 할당을 조사할 때 유용합니다. 많은 임시 객체를 할당하는 것은 상당한 오버헤드를 발생시킬 수 있으므로, 할당 비율을 줄이면 성능이 크게 향상됩니다.
녹화된 객체 뷰에서 가비지 수집된 객체를 표시하려면, 활성도 선택기를 가비지 수집된 객체 또는 활성 및 가비지 수집된 객체로 변경하십시오. 할당 호출 트리 및 할당 핫스팟 뷰의 옵션 대화 상자에는 동등한 드롭다운이 있습니다.
그러나 JProfiler는 기본적으로 가비지 수집된 객체에 대한 할당 트리 정보를 수집하지 않습니다. 이는 활성 객체에 대한 데이터만 유지하는 것이 훨씬 적은 오버헤드로 가능하기 때문입니다. "할당 호출 트리" 또는 "할당 핫스팟" 뷰에서 가비지 수집된 객체를 포함하는 모드로 활성도 선택기를 전환할 때, JProfiler는 녹화 유형을 변경할 것을 제안합니다. 이는 프로파일링 설정의 변경 사항이므로, 즉시 변경을 적용하기로 선택하면 이전에 녹화된 모든 데이터가 지워집니다. 미리 이 설정을 변경하고 싶다면, 세션 설정 대화 상자의 "고급 설정" -> "메모리 프로파일링"에서 그렇게 할 수 있습니다.
다음 정류장: 힙 워커
더 고급 유형의 질문은 객체 간의 참조를 포함합니다. 예를 들어, 녹화된 객체, 할당 트리 및 할당 핫스팟 뷰에 표시되는 크기는 얕은 크기입니다. 이는 클래스의 메모리 레이아웃만 포함하며, 참조된 클래스는 포함하지 않습니다. 클래스의 객체가 실제로 얼마나 무거운지를 보려면, 종종 유지된 크기를 알고 싶습니다. 이는 해당 객체가 힙에서 제거될 경우 해제될 메모리 양을 의미합니다.
이러한 종류의 정보는 라이브 메모리 뷰에서는 사용할 수 없습니다. 이는 힙의 모든 객체를 열거하고 비용이 많이 드는 계산을 수행해야 하기 때문입니다. 이 작업은 힙 워커가 처리합니다. 라이브 메모리 뷰의 관심 지점에서 힙 워커로 이동하려면, 힙 워커에서 표시 도구 모음 버튼을 사용할 수 있습니다. 이는 힙 워커의 동등한 뷰로 이동합니다.
힙 스냅샷이 없는 경우, 새 힙 스냅샷이 생성되며, 그렇지 않으면 JProfiler는 기존 힙 스냅샷을 사용할지 여부를 묻습니다.
어쨌든, 라이브 메모리 뷰와 힙 워커의 숫자가 종종 매우 다를 것임을 이해하는 것이 중요합니다. 힙 워커가 라이브 메모리 뷰와 다른 시점의 스냅샷을 보여주는 것 외에도, 모든 참조되지 않은 객체를 제거하기 때문입니다. 가비지 수집기의 상태에 따라, 참조되지 않은 객체는 힙의 상당 부분을 차지할 수 있습니다.