자바 메모리 누수 사냥

게시 됨: 2022-03-11

경험이 없는 프로그래머는 종종 Java의 자동 가비지 수집이 메모리 관리에 대한 걱정에서 완전히 해방된다고 생각합니다. 이것은 일반적인 오해입니다. 가비지 수집기가 최선을 다하는 동안 최고의 프로그래머라도 심각한 메모리 누수의 희생물이 될 가능성이 있습니다. 설명하겠습니다.

더 이상 필요하지 않은 개체 참조가 불필요하게 유지될 때 메모리 누수가 발생합니다. 이러한 누출은 나쁩니다. 하나는 프로그램이 점점 더 많은 리소스를 소비함에 따라 시스템에 불필요한 압력을 가합니다. 설상가상으로 이러한 누출을 감지하는 것이 어려울 수 있습니다. 정적 분석은 종종 이러한 중복 참조를 정확하게 식별하는 데 어려움을 겪으며 기존 누출 감지 도구는 개별 개체에 대한 세분화된 정보를 추적 및 보고하여 해석하기 어렵고 정밀도가 부족한 결과를 생성합니다.

즉, 누출은 식별하기 너무 어렵거나 유용하기에는 너무 구체적인 용어로 식별됩니다.

실제로 유사하고 겹치는 증상이 있는 네 가지 범주의 메모리 문제가 있지만 원인과 해결 방법은 다양합니다.

  • 성능 : 일반적으로 과도한 개체 생성 및 삭제, 가비지 수집의 긴 지연, 과도한 운영 체제 페이지 스와핑 등과 관련이 있습니다.

  • 리소스 제약 : 사용 가능한 메모리가 거의 없거나 메모리가 너무 조각화되어 큰 개체를 할당할 수 없을 때 발생합니다. 이는 네이티브이거나 더 일반적으로 Java 힙 관련일 수 있습니다.

  • Java 힙 누수 : Java 객체가 릴리스되지 않고 계속 생성되는 고전적인 메모리 누수입니다. 이것은 일반적으로 잠재 개체 참조로 인해 발생합니다.

  • 기본 메모리 누수 : JNI 코드, 드라이버 또는 JVM 할당에 의한 할당과 같이 Java 힙 외부에서 지속적으로 증가하는 메모리 사용과 관련됩니다.

이 메모리 관리 자습서에서는 Java 힙 누출에 초점을 맞추고 Java VisualVM 보고서를 기반으로 이러한 누출을 감지하고 실행 중인 Java 기술 기반 애플리케이션을 분석하기 위한 시각적 인터페이스를 활용하는 접근 방식을 간략하게 설명합니다.

그러나 메모리 누수를 예방하고 찾기 전에 발생 방법과 이유를 이해해야 합니다. ( 참고: 메모리 누수의 복잡성에 대해 잘 알고 있다면 건너뛸 수 있습니다. )

메모리 누수: 입문서

우선 메모리 누수를 질병으로 생각하고 Java의 OutOfMemoryError (OOM, 간결함)를 증상으로 생각하십시오. 그러나 모든 질병과 마찬가지로 모든 OOM이 반드시 메모리 누수를 의미하는 것은 아닙니다 . OOM은 다수의 로컬 변수 또는 기타 이러한 이벤트의 생성으로 인해 발생할 수 있습니다. 반면에 모든 메모리 누수가 반드시 OOM으로 나타나는 것은 아닙니다 . 특히 데스크톱 응용 프로그램이나 클라이언트 응용 프로그램(다시 시작하지 않고는 오래 실행되지 않음)의 경우에는 더욱 그렇습니다.

메모리 누수를 질병으로 생각하고 OutOfMemoryError를 증상으로 생각하십시오. 그러나 모든 OutOfMemoryError가 메모리 누수를 의미하는 것은 아니며 모든 메모리 누수가 OutOfMemoryError로 나타나는 것은 아닙니다.

이 누출이 왜 그렇게 나쁜가요? 무엇보다도 프로그램 실행 중 메모리 블록이 누출되면 시간이 지남에 따라 시스템 성능이 저하되는 경우가 많습니다. 할당되었지만 사용하지 않은 메모리 블록은 시스템에 사용 가능한 물리적 메모리가 부족해지면 교체해야 하기 때문입니다. 결국 프로그램은 사용 가능한 가상 주소 공간을 소진하여 OOM을 발생시킬 수도 있습니다.

OutOfMemoryError 해독

위에서 언급했듯이 OOM은 메모리 누수의 일반적인 표시입니다. 기본적으로 새 개체를 할당할 공간이 충분하지 않으면 오류가 발생합니다. 가비지 수집기가 필요한 공간을 찾지 못하고 힙을 더 이상 확장할 수 없습니다. 따라서 스택 추적과 함께 오류가 발생합니다.

OOM 진단의 첫 번째 단계는 오류가 실제로 무엇을 의미하는지 확인하는 것입니다. 이것은 당연한 것처럼 들리지만 대답이 항상 그렇게 명확하지는 않습니다. 예: Java 힙이 가득 찼기 때문에 OOM이 표시됩니까? 아니면 기본 힙이 가득 찼습니까? 이 질문에 답하는 데 도움이 되도록 몇 가지 가능한 오류 메시지를 분석해 보겠습니다.

  • java.lang.OutOfMemoryError: Java heap space

  • java.lang.OutOfMemoryError: PermGen space

  • java.lang.OutOfMemoryError: Requested array size exceeds VM limit

  • java.lang.OutOfMemoryError: request <size> bytes for <reason>. Out of swap space?

  • java.lang.OutOfMemoryError: <reason> <stack trace> (Native method)

"Java 힙 공간"

이 오류 메시지가 반드시 메모리 누수를 의미하는 것은 아닙니다. 사실 문제는 구성 문제만큼 간단할 수 있습니다.

예를 들어, 저는 이러한 유형의 OutOfMemoryError 를 지속적으로 생성하는 응용 프로그램을 분석하는 일을 담당했습니다. 약간의 조사 후에, 나는 범인이 너무 많은 메모리를 요구하는 어레이 인스턴스화라는 것을 알아냈습니다. 이 경우 응용 프로그램의 오류가 아니라 응용 프로그램 서버가 너무 작은 기본 힙 크기에 의존하고 있었습니다. JVM의 메모리 매개변수를 조정하여 문제를 해결했습니다.

다른 경우, 특히 수명이 긴 응용 프로그램의 경우 메시지는 의도하지 않게 개체에 대한 참조를 보유하고 있다는 표시일 수 있으며 가비지 수집기가 개체 를 정리하지 못하도록 방지합니다. 이것은 메모리 누수에 해당하는 Java 언어입니다 . ( 참고: 응용 프로그램에서 호출한 API는 의도하지 않게 개체 참조를 보유할 수도 있습니다. )

이러한 "자바 힙 공간" OOM의 또 다른 잠재적인 소스는 종료 자 사용과 함께 발생합니다. 클래스에 finalize 메서드가 있는 경우 해당 유형의 개체는 가비지 수집 시 회수된 공간을 갖지 않습니다. 대신, 가비지 수집 후 개체는 나중에 발생하는 마무리를 위해 대기열에 추가됩니다. Sun 구현에서 종료자는 데몬 스레드에 의해 실행됩니다. 종료자 스레드가 종료 큐를 따라갈 수 없으면 Java 힙이 가득 차고 OOM이 발생할 수 있습니다.

“펌젠 공간”

이 오류 메시지는 영구 생성이 가득 찼음을 나타냅니다. 영구 생성은 클래스 및 메서드 개체를 저장하는 힙 영역입니다. 애플리케이션이 많은 수의 클래스를 로드하는 경우 -XX:MaxPermSize 옵션을 사용하여 영구 생성 크기를 늘려야 할 수 있습니다.

내부 java.lang.String 객체도 영구 생성에 저장됩니다. java.lang.String 클래스는 문자열 풀을 유지 관리합니다. 인턴 메서드가 호출되면 메서드는 풀을 확인하여 동등한 문자열이 있는지 확인합니다. 그렇다면 인턴 메서드에서 반환됩니다. 그렇지 않은 경우 문자열이 풀에 추가됩니다. 보다 정확한 용어로 java.lang.String.intern 메소드는 문자열의 표준 표현을 리턴합니다. 결과는 해당 문자열이 리터럴로 나타날 경우 반환되는 동일한 클래스 인스턴스에 대한 참조입니다. 응용 프로그램이 많은 수의 문자열을 인턴하는 경우 영구 생성의 크기를 늘려야 할 수 있습니다.

참고: jmap -permgen 명령을 사용하여 내부화된 String 인스턴스에 대한 정보를 포함하여 영구 생성과 관련된 통계를 인쇄할 수 있습니다.

"요청한 어레이 크기가 VM 제한을 초과합니다."

이 오류는 응용 프로그램(또는 해당 응용 프로그램에서 사용하는 API)이 힙 크기보다 큰 배열을 할당하려고 시도했음을 나타냅니다. 예를 들어 응용 프로그램이 512MB의 배열을 할당하려고 시도하지만 최대 힙 크기가 256MB인 경우 이 오류 메시지와 함께 OOM이 발생합니다. 대부분의 경우 문제는 구성 문제이거나 애플리케이션이 대규모 어레이를 할당하려고 할 때 발생하는 버그입니다.

"<reason>에 대한 <size>바이트 요청. 스왑 공간이 부족합니까?”

이 메시지는 OOM인 것 같습니다. 그러나 HotSpot VM은 기본 힙에서 할당이 실패하고 기본 힙이 거의 소진된 경우 이 명백한 예외를 발생시킵니다. 메시지에는 실패한 요청의 크기(바이트)와 메모리 요청 이유가 포함됩니다. 대부분의 경우 <이유>는 할당 실패를 보고하는 소스 모듈의 이름입니다.

이러한 유형의 OOM이 발생하면 운영 체제에서 문제 해결 유틸리티를 사용하여 문제를 추가로 진단해야 할 수 있습니다. 어떤 경우에는 문제가 응용 프로그램과 관련이 없을 수도 있습니다. 예를 들어 다음과 같은 경우 이 오류가 표시될 수 있습니다.

  • 운영 체제가 스왑 공간이 충분하지 않게 구성되었습니다.

  • 시스템의 다른 프로세스가 사용 가능한 모든 메모리 리소스를 사용하고 있습니다.

네이티브 누수로 인해 애플리케이션이 실패했을 수도 있습니다(예: 애플리케이션 또는 라이브러리 코드의 일부가 지속적으로 메모리를 할당하지만 운영 체제에 해제하지 못하는 경우).

<이유> <스택 트레이스> (네이티브 방식)

이 오류 메시지가 표시되고 스택 추적의 상단 프레임이 기본 메서드인 경우 해당 기본 메서드에 할당 오류가 발생한 것입니다. 이 메시지와 이전 메시지의 차이점은 Java 메모리 할당 실패가 Java VM 코드가 아닌 JNI 또는 기본 메소드에서 감지되었다는 것입니다.

이러한 유형의 OOM이 발생하면 운영 체제의 유틸리티를 사용하여 문제를 추가로 진단해야 할 수 있습니다.

OOM 없이 애플리케이션 충돌

경우에 따라 기본 힙에서 할당이 실패한 직후 애플리케이션이 충돌할 수 있습니다. 이것은 메모리 할당 함수에서 반환된 오류를 확인하지 않는 네이티브 코드를 실행 중인 경우에 발생합니다.

예를 들어, malloc 시스템 호출은 사용 가능한 메모리가 없는 경우 NULL 을 반환합니다. malloc 에서 반환이 확인되지 않으면 응용 프로그램이 잘못된 메모리 위치에 액세스하려고 할 때 충돌이 발생할 수 있습니다. 상황에 따라 이러한 유형의 문제는 찾기 어려울 수 있습니다.

어떤 경우에는 치명적인 오류 로그 또는 크래시 덤프의 정보로 충분합니다. 충돌의 원인이 일부 메모리 할당에서 오류 처리 부족으로 확인되면 해당 할당 실패의 원인을 찾아내야 합니다. 다른 기본 힙 문제와 마찬가지로 시스템이 스왑 공간이 충분하지 않게 구성되어 있을 수 있으며, 다른 프로세스에서 사용 가능한 모든 메모리 리소스를 사용하고 있을 수 있습니다.

누출 진단

대부분의 경우 메모리 누수를 진단하려면 해당 응용 프로그램에 대한 매우 자세한 지식이 필요합니다. 경고: 프로세스는 길고 반복적일 수 있습니다.

메모리 누수를 찾기 위한 우리의 전략은 비교적 간단합니다.

  1. 증상 확인

  2. 자세한 가비지 수집 활성화

  3. 프로파일링 활성화

  4. 추적 분석

1. 증상 확인

논의한 바와 같이, 많은 경우에 Java 프로세스는 결국 OOM 런타임 예외를 발생시킵니다. 이는 메모리 리소스가 고갈되었다는 명확한 표시입니다. 이 경우 정상적인 메모리 소진과 누수를 구별해야 합니다. OOM의 메시지를 분석하고 위에 제공된 토론을 기반으로 범인을 찾으십시오.

종종 Java 애플리케이션이 런타임 힙이 제공하는 것보다 더 많은 스토리지를 요청하는 경우 잘못된 설계 때문일 수 있습니다. 예를 들어, 애플리케이션이 이미지의 여러 복사본을 생성하거나 파일을 배열로 로드하는 경우 이미지나 파일이 매우 클 때 스토리지가 부족해집니다. 이것은 정상적인 리소스 소진입니다. 응용 프로그램은 설계된 대로 작동합니다(이 디자인은 분명히 골치거리지만).

그러나 응용 프로그램이 동일한 종류의 데이터를 처리하는 동안 메모리 사용률을 꾸준히 늘리면 메모리 누수가 발생할 수 있습니다.

2. 자세한 가비지 컬렉션 활성화

실제로 메모리 누수가 있다고 주장하는 가장 빠른 방법 중 하나는 자세한 가비지 수집을 활성화하는 것입니다. 메모리 제약 문제는 일반적으로 verbosegc 출력의 패턴을 검사하여 식별할 수 있습니다.

특히 -verbosegc 인수를 사용하면 GC(가비지 수집) 프로세스가 시작될 때마다 추적을 생성할 수 있습니다. 즉, 메모리가 가비지 수집될 때 요약 보고서가 표준 오류로 인쇄되어 메모리가 어떻게 관리되고 있는지 알 수 있습니다.

다음은 –verbosegc 옵션으로 생성된 몇 가지 일반적인 출력입니다.

자세한 가비지 수집 출력

이 GC 추적 파일의 각 블록(또는 스탠자)은 오름차순으로 번호가 매겨집니다. 이 추적을 이해하려면 연속적인 할당 실패 스탠자를 살펴보고 총 메모리(여기서는 19725304)가 증가하는 동안 시간이 지남에 따라 감소하는 해제된 메모리(바이트 및 백분율)를 찾아야 합니다. 이것은 기억력 고갈의 전형적인 징후입니다.

3. 프로파일링 활성화

다른 JVM은 일반적으로 개체의 유형 및 크기에 대한 자세한 정보를 포함하는 힙 활동을 반영하기 위해 추적 파일을 생성하는 다양한 방법을 제공합니다. 이것을 힙 프로파일링이라고 합니다 .

4. 추적 분석

이 게시물은 Java VisualVM에서 생성된 추적에 중점을 둡니다. 추적은 다양한 Java 메모리 누수 감지 도구에 의해 생성될 수 있으므로 다양한 형식으로 제공될 수 있지만 그 이면의 아이디어는 항상 동일합니다. 힙에서 존재해서는 안 되는 객체 블록을 찾고 이러한 객체가 누적되는지 확인합니다. 풀어주는 대신. 특정 이벤트가 Java 응용 프로그램에서 트리거될 때마다 할당되는 것으로 알려진 일시적인 개체가 특히 중요합니다. 소량만 존재해야 하는 많은 개체 인스턴스의 존재는 일반적으로 응용 프로그램 버그를 나타냅니다.

마지막으로 메모리 누수를 해결하려면 코드를 철저히 검토해야 합니다. 개체 누출 유형에 대해 알아두면 매우 도움이 될 수 있으며 디버깅 속도를 상당히 높일 수 있습니다.

JVM에서 가비지 컬렉션은 어떻게 작동합니까?

메모리 누수 문제가 있는 애플리케이션에 대한 분석을 시작하기 전에 먼저 JVM에서 가비지 수집이 작동하는 방식을 살펴보겠습니다.

JVM은 기본적으로 주변 세계를 일시 중지하고 모든 루트 개체(실행 중인 스레드에서 직접 참조하는 개체)를 표시하고 해당 참조를 따라 이동하면서 보는 각 개체를 표시하는 방식으로 작동하는 추적 수집기라고 하는 가비지 수집기의 형태를 사용합니다.

Java는 생성 된 대부분의 객체가 빠르게 폐기되고 빠르게 수집되지 않는 객체는 잠시 동안 주변에 있을 가능성이 높다는 세대 가설 가정을 기반으로 하는 세대별 가비지 수집기라는 것을 구현합니다.

이 가정을 기반으로 Java는 객체를 여러 세대로 분할합니다. 다음은 시각적 해석입니다.

Java 파티션을 여러 세대로 나누기

  • Young Generation - 이것은 개체가 시작되는 곳입니다. 두 개의 하위 세대가 있습니다.

    • Eden Space - 여기서부터 개체가 시작됩니다. 대부분의 객체는 Eden Space에서 생성되고 파괴됩니다. 여기서 GC는 최적화된 가비지 수집인 Minor GC 를 수행합니다. Minor GC가 수행될 때 여전히 필요한 객체에 대한 참조는 생존 공간(S0 또는 S1) 중 하나로 마이그레이션됩니다.

    • Survivor Space(S0 및 S1) - Eden에서 살아남은 개체가 여기에 있습니다. 이 중 두 개가 있으며 주어진 시간에 하나만 사용됩니다(심각한 메모리 누수가 있는 경우 제외). 하나는 empty 로 지정되고 다른 하나는 live 로 지정되며 모든 GC 주기와 교대합니다.

  • Tenured Generation - 구세대(그림 2의 구 공간)라고도 하는 이 공간은 수명이 더 긴 오래된 개체를 보유합니다(충분히 오래 사는 경우 생존 공간에서 옮겨짐). 이 공간이 가득 차면 GC는 성능 면에서 더 많은 비용이 드는 Full GC 를 수행합니다. 이 공간이 제한 없이 커지면 JVM은 OutOfMemoryError - Java heap space 을 발생시킵니다.

  • 영구 세대 - 종신 세대와 밀접하게 관련된 3세대인 영구 세대는 Java 언어 수준에서 동등하지 않은 객체를 설명하기 위해 가상 머신에 필요한 데이터를 보유하기 때문에 특별합니다. 예를 들어 클래스와 메서드를 설명하는 객체는 영구 생성에 저장됩니다.

Java는 각 세대에 서로 다른 가비지 수집 방법을 적용할 만큼 충분히 똑똑합니다. 젊은 세대는 Parallel New Collector 라는 추적 복사 수집기 를 사용하여 처리됩니다. 이 컬렉터는 세상을 멈추게 하지만 일반적으로 젊은 세대가 작기 때문에 멈춤이 짧다.

JVM 생성 및 작동 방식에 대한 자세한 내용은 Java HotSpot 가상 머신 설명서의 메모리 관리를 참조하십시오.

메모리 누수 감지

메모리 누수를 찾아 제거하려면 적절한 메모리 누수 도구가 필요합니다. Java VisualVM을 사용하여 이러한 누출을 감지하고 제거할 때입니다.

Java VisualVM을 사용하여 원격으로 힙 프로파일링

VisualVM은 실행 중인 Java 기술 기반 응용 프로그램에 대한 자세한 정보를 볼 수 있는 시각적 인터페이스를 제공하는 도구입니다.

VisualVM을 사용하면 로컬 애플리케이션 및 원격 호스트에서 실행되는 애플리케이션과 관련된 데이터를 볼 수 있습니다. JVM 소프트웨어 인스턴스에 대한 데이터를 캡처하고 로컬 시스템에 저장할 수도 있습니다.

Java VisualVM의 모든 기능을 활용하려면 Java SE(Java Platform, Standard Edition) 버전 6 이상을 실행해야 합니다.

관련: 이미 Java 8로 업그레이드해야 하는 이유

JVM에 대한 원격 연결 활성화

프로덕션 환경에서는 코드가 실행될 실제 시스템에 액세스하기 어려운 경우가 많습니다. 운 좋게도 원격으로 Java 애플리케이션을 프로파일링할 수 있습니다.

먼저 대상 머신에 대한 JVM 액세스 권한을 자신에게 부여해야 합니다. 이렇게 하려면 다음 내용으로 jstatd.all.policy 라는 파일을 만듭니다.

 grant codebase "file:${java.home}/../lib/tools.jar" { permission java.security.AllPermission; };

파일이 생성되면 다음과 같이 jstatd - Virtual Machine jstat Daemon 도구를 사용하여 대상 VM에 대한 원격 연결을 활성화해야 합니다.

 jstatd -p <PORT_NUMBER> -J-Djava.security.policy=<PATH_TO_POLICY_FILE>

예를 들어:

 jstatd -p 1234 -J-Djava.security.policy=D:\jstatd.all.policy

대상 VM에서 시작된 jstatd 로 대상 시스템에 연결하고 메모리 누수 문제가 있는 애플리케이션을 원격으로 프로파일링할 수 있습니다.

원격 호스트에 연결

클라이언트 컴퓨터에서 프롬프트를 열고 jvisualvm 을 입력하여 VisualVM 도구를 엽니다.

다음으로 VisualVM에 원격 호스트를 추가해야 합니다. J2SE 6 이상이 설치된 다른 시스템에서 원격 연결을 허용하도록 대상 JVM이 활성화되어 있으므로 Java VisualVM 도구를 시작하고 원격 호스트에 연결합니다. 원격 호스트와의 연결에 성공하면 다음과 같이 대상 JVM에서 실행 중인 Java 애플리케이션이 표시됩니다.

대상 jvm에서 실행

응용 프로그램에서 메모리 프로파일러를 실행하려면 측면 패널에서 이름을 두 번 클릭하면 됩니다.

이제 메모리 분석기가 모두 설정 되었으므로 MemLeak 이라고 하는 메모리 누수 문제가 있는 응용 프로그램을 조사해 보겠습니다.

멤리크

물론 Java에서 메모리 누수를 만드는 방법에는 여러 가지가 있습니다. 단순화를 위해 클래스를 HashMap 의 키로 정의하지만 equals() 및 hashcode() 메서드는 정의하지 않습니다.

HashMap은 Map 인터페이스에 대한 해시 테이블 구현이며 키와 값의 기본 개념을 정의합니다. 각 값은 고유 키와 관련되어 있으므로 주어진 키-값 쌍의 키가 이미 HashMap, 현재 값이 대체됩니다.

키 클래스가 equals()hashcode() 메서드의 올바른 구현을 제공하는 것은 필수입니다. 그것들이 없으면 좋은 키가 생성된다는 보장이 없습니다.

equals()hashcode() 메서드를 정의하지 않음으로써 HashMap에 동일한 키를 계속해서 추가하고, 키를 교체하는 대신 HashMap이 계속 증가하여 이러한 동일한 키를 식별하지 못하고 OutOfMemoryError 발생합니다. .

다음은 MemLeak 클래스입니다.

 package com.post.memory.leak; import java.util.Map; public class MemLeak { public final String key; public MemLeak(String key) { this.key =key; } public static void main(String args[]) { try { Map map = System.getProperties(); for(;;) { map.put(new MemLeak("key"), "value"); } } catch(Exception e) { e.printStackTrace(); } } }

참고: 메모리 누수는 14행의 무한 루프로 인한 것이 아닙니다 . 무한 루프는 리소스 고갈로 이어질 수 있지만 메모리 누수는 아닙니다. equals()hashcode() 메서드를 제대로 구현했다면 HashMap 내부에 하나의 요소만 있기 때문에 무한 루프에서도 코드가 제대로 실행될 것입니다.

(관심 있는 분들을 위해 (의도적으로) 누출을 생성하는 몇 가지 대안이 있습니다.)

자바 VisualVM 사용

Java VisualVM을 사용하여 Java 힙을 메모리 모니터링하고 해당 동작이 메모리 누수를 나타내는지 식별할 수 있습니다.

다음은 초기화 직후 MemLeak의 Java 힙 분석기를 그래픽으로 나타낸 것입니다(다양한 세대에 대한 논의를 상기하십시오).

Java Visualvm을 사용하여 메모리 누수 모니터링

단 30초 후 Old Generation은 거의 가득 찼습니다. 이는 Full GC를 사용하더라도 Old Generation이 계속 증가하고 있음을 나타내는 메모리 누수의 명백한 신호입니다.

이 누출의 원인을 감지하는 한 가지 방법은 heapdump 가 있는 Java VisualVM을 사용하여 생성된 다음 이미지( 확대하려면 클릭 )에 나와 있습니다. 여기에서 Hashtable$Entry 개체의 50%가 힙 에 있는 반면 두 번째 줄은 MemLeak 클래스를 가리킵니다. 따라서 메모리 누수는 MemLeak 클래스 내에서 사용되는 해시 테이블 로 인해 발생합니다.

해시 테이블 메모리 누수

마지막으로, Young 및 Old 세대가 완전히 가득 찬 OutOfMemoryError 직후 Java 힙을 관찰하십시오.

메모리 부족 오류

결론

메모리 누수는 증상이 다양하고 재현하기 어렵기 때문에 해결하기 가장 어려운 Java 응용 프로그램 문제 중 하나입니다. 여기에서 메모리 누수를 발견하고 그 소스를 식별하는 단계별 접근 방식을 설명했습니다. 그러나 무엇보다도 오류 메시지를 자세히 읽고 스택 추적에 주의하십시오. 모든 누출이 보이는 것처럼 단순하지는 않습니다.

부록

Java VisualVM과 함께 메모리 누수 감지를 수행할 수 있는 몇 가지 다른 도구가 있습니다. 많은 누수 감지기는 메모리 관리 루틴에 대한 호출을 가로채서 라이브러리 수준에서 작동합니다. 예를 들어, HPROF 는 힙 및 CPU 프로파일링을 위해 J2SE(Java 2 Platform Standard Edition)와 함께 번들로 제공되는 간단한 명령줄 도구입니다. HPROF 의 출력은 직접 분석하거나 JHAT 와 같은 다른 도구에 대한 입력으로 사용할 수 있습니다. J2EE(Java 2 Enterprise Edition) 애플리케이션으로 작업할 때 Websphere 애플리케이션 서버용 IBM Heapdumps와 같이 보다 친숙한 힙 덤프 분석기 솔루션이 많이 있습니다.