안드로이드 메모리 누출 요약
메모리 관리의 목적은 개발 중 응용 프로그램의 메모리 누출을 효과적으로 피하는 데 도움이됩니다. 모두는 메모리 누출에 익숙합니다. 간단히 말해서, 릴리스 해야하는 객체가 릴리스되지 않았으며 특정 인스턴스에 의해 유지되었지만 더 이상 사용되지 않으므로 GC를 재활용 할 수 없음을 의미합니다. 최근에는 관련 문서와 자료를 많이 읽었습니다. 나는 그들을 요약하고 정착시키고 공유하고 배우고, 미래의 코딩 중에 이러한 상황을 피하는 방법에 대한 경고를하고 응용 프로그램의 경험과 품질을 향상시킬 계획입니다.
Java 메모리 누출의 기본 사항부터 시작하여 특정 예제를 사용하여 Android의 메모리 누출의 다양한 원인을 설명하고 도구를 사용하여 응용 프로그램 메모리 누출을 분석하고 마지막으로 요약합니다.
Java 메모리 할당 전략
Java 프로그램이 실행될 때 세 가지 유형의 메모리 할당 전략, 즉 정적 할당, 스택 할당 및 힙 할당이 있습니다. 이에 따라 세 가지 스토리지 전략에 사용되는 메모리 공간은 주로 정적 저장 영역 (메소드 영역이라고도 함), 스택 영역 및 힙 영역입니다.
정적 저장 영역 (방법 영역) : 주로 정적 데이터, 글로벌 정적 데이터 및 상수를 저장합니다. 이 메모리는 프로그램이 컴파일 될 때 할당되어 프로그램 실행 전체에 걸쳐 존재합니다.
스택 영역 : 메소드가 실행되면 메소드 본문의 로컬 변수 (기본 데이터 유형 및 객체 참조 포함)가 스택에 생성되며 이러한 로컬 변수가 보유한 메모리는 메소드 실행 끝에서 자동으로 해제됩니다. 스택 메모리 할당 작업이 프로세서의 명령 세트에 내장되어 있으므로 매우 효율적이지만 할당 된 메모리 용량은 제한됩니다.
힙 영역 : 동적 메모리 할당이라고도하는 것은 일반적으로 프로그램이 실행 중일 때 직접 새로운 메모리, 즉 객체 인스턴스 인 인스턴스를 나타냅니다. 메모리 의이 부분이 사용되지 않으면 Java Garbage Collector는 재활용을 담당합니다.
스택과 힙의 차이점 :
메소드 본문과 객체의 기준 변수에 정의 된 일부 기본 유형의 변수는 메소드의 스택 메모리에 할당됩니다. 변수가 메소드 블록에 정의되면 Java는 스택의 변수에 대한 메모리 공간을 할당합니다. 변수의 범위가 초과되면 변수가 유효하지 않고 메모리 공간이 해제되고 메모리 공간이 재사용 될 수 있습니다.
힙 메모리는 새 (객체의 모든 멤버 변수 포함) 및 배열로 생성 된 모든 객체를 저장하는 데 사용됩니다. 힙에 할당 된 메모리는 Java Garbage Collector에 의해 자동으로 관리됩니다. 힙에 어레이 또는 객체가 생성 된 후 스택에서 특수 변수를 정의 할 수 있습니다. 이 변수의 값은 힙 메모리의 어레이 또는 객체의 첫 번째 주소와 같습니다. 이 특수 변수는 위에서 언급 한 참조 변수입니다. 이 참조 변수를 통해 힙의 객체 또는 어레이에 액세스 할 수 있습니다.
예를 들어:
공개 클래스 샘플 {int s1 = 0; sample msample1 = new Sample (); public void method () {int s2 = 1; sample msample2 = new Sample ();}} 샘플 MSAMPLE3 = new Sample (); 샘플 클래스의 로컬 변수 S2와 기준 변수 MSAMPLE2는 모두 스택에 존재하지만 MSAMPLE2에 의해 가리키는 물체는 힙에 존재합니다.
MSAMPLE3에 의해 지적 된 객체 엔티티는이 객체의 모든 멤버 변수 S1 및 MSAMPLE1을 포함하여 힙에 저장되며 스택에 자체적으로 존재합니다.
결론적으로 :
로컬 변수의 기본 데이터 유형과 참조는 스택에 저장되며 참조 된 객체 엔티티는 힙에 저장됩니다. - 메소드의 변수에 속하기 때문에 수명주기는 메소드로 끝납니다.
멤버 변수는 모두 저장되고 힙에 (기본 데이터 유형, 참조 및 참조 된 객체 엔티티 포함) - 클래스에 속하기 때문에 클래스 객체는 결국 새로운 용도로 사용됩니다.
Java의 메모리 할당을 이해 한 후 Java가 메모리를 관리하는 방법을 살펴 보겠습니다.
Java가 메모리를 관리하는 방법
Java의 메모리 관리는 객체 할당 및 릴리스 문제입니다. Java에서 프로그래머는 키워드 새로운 (기본 유형 제외)를 통해 각 객체에 대한 메모리 공간을 신청해야하며 모든 객체는 힙에 공간을 할당합니다 (힙). 또한, 객체의 방출은 GC에 의해 결정되고 실행된다. Java에서는 메모리 할당이 프로그램에 의해 수행되며 메모리 릴리스는 GC에 의해 수행됩니다. 이 두 줄의 수익 및 지출 방법은 프로그래머의 작업을 단순화합니다. 그러나 동시에 JVM의 작업에 추가됩니다. 이것은 또한 Java 프로그램이 느리게 실행되는 이유 중 하나입니다. 객체를 올바르게 릴리스하려면 GC가 응용 프로그램, 인용, 인용, 할당 등을 포함하여 각 객체의 실행 상태를 모니터링해야하며 GC는이를 모니터링해야합니다.
객체의 상태를 모니터링하는 것은 객체를보다 정확하고 적시에 해제하는 것이며, 객체를 공개하는 기본 원칙은 객체가 더 이상 참조되지 않는다는 것입니다.
GC의 작동 방식을 더 잘 이해하기 위해 객체를 지시 된 그래프의 정점으로 고려할 수 있으며, 참조 자에서 참조 된 객체로 가리키는 그래프의 가장자리로 참조 관계를 고려할 수 있습니다. 또한 각 스레드 객체는 그래프의 시작 정점으로 사용할 수 있습니다. 예를 들어, 대부분의 프로그램은 기본 프로세스에서 시작하므로 그래프는 기본 프로세스 정점으로 시작하는 루트 트리입니다. 이 지시 된 그래프에서 루트 정점에서 도달 할 수있는 객체는 유효한 객체이며 GC는 이러한 객체를 재활용하지 않습니다. 이 루트 정점에서 객체 (연결된 서브 그래프)가 도달 할 수없는 경우 (그래프는 지시 된 그래프임을 참고), 우리는이 객체가 더 이상 참조되지 않으며 GC에 의해 재활용 될 수 있다고 생각합니다.
아래에서는 지시 된 그래프를 사용하여 메모리 관리를 나타내는 방법에 대한 예를 제공합니다. 프로그램의 모든 순간에 대해 JVM의 메모리 할당을 나타내는 지시 된 그래프가 있습니다. 아래 그림은 6 행으로 실행되는 왼쪽의 프로그램 다이어그램입니다.
Java는 메모리 관리를 위해 지시 된 그래프를 사용하여 참조 루프의 문제를 제거 할 수 있습니다. 예를 들어, 서로를 참조하는 세 가지 객체가 있습니다. 그것들과 루트 과정이 도달 할 수없는 한, GC는 또한 그것을 재활용 할 수 있습니다. 이 방법의 장점은 메모리 관리에서 정확성이 높지만 효율이 낮다는 것입니다. 일반적으로 사용되는 또 다른 메모리 관리 기술은 카운터를 사용하는 것입니다. 예를 들어, COM 모델은 카운터 메소드를 사용하여 구성 요소를 관리합니다. 지시 된 그래프와 비교할 때 정밀 선이 낮으며 (원형 참조 문제를 다루기가 어렵지만) 실행 효율이 높습니다.
Java의 메모리 누출은 무엇입니까?
Java에서 메모리 누출은 할당 된 일부 객체의 존재이며, 여기에는 다음 두 가지 특성이 있습니다. 첫째, 이러한 객체에 도달 할 수 있습니다. 즉, 지시 된 그래프에는 연결할 수있는 경로가 있습니다. 둘째, 이러한 객체는 쓸모가 없습니다. 즉, 프로그램은 앞으로이 객체를 다시 사용하지 않을 것입니다. 물체 가이 두 조건을 충족하면,이 객체는 Java에서 메모리 누출로 결정될 수 있으며, 이러한 물체는 GC에 의해 재활용되지 않지만 메모리를 차지합니다.
C ++에서 메모리 누출은 범위가 더 큽니다. 일부 객체는 메모리 공간을 할당하지만 도달 할 수 없습니다. C ++에는 GC가 없기 때문에 이러한 메모리는 수집되지 않습니다. Java에서는 이러한 도달 할 수없는 물체는 GC에 의해 재활용되므로 프로그래머는 메모리 누출 의이 부분을 고려할 필요가 없습니다.
분석을 통해 C ++의 경우 프로그래머는 자체적으로 가장자리와 정점을 관리해야하며 Java 프로그래머의 경우 가장자리를 관리하면됩니다 (정점 출시를 관리 할 필요는 없습니다). 이런 식으로 Java는 프로그래밍 효율성을 향상시킵니다.
따라서 위의 분석을 통해 Java에는 메모리 누출이 있지만 범위는 C ++의 범위보다 작습니다. Java Language는 모든 객체가 도달 할 수 있음을 보장하기 때문에 모든 도달 할 수없는 객체는 GC에 의해 관리됩니다.
프로그래머의 경우 GC는 기본적으로 투명하고 보이지 않습니다. Java Language Specification 정의에 따라 GC를 실행하는 System.gc ()와 같은 GC에 액세스 할 수있는 몇 가지 기능 만 있지만이 기능은 JVM의 가비지 수집가가 실행 될 것이라고 보장하지 않습니다. 다른 JVM 구현자는 다른 알고리즘을 사용하여 GC를 관리 할 수 있기 때문입니다. 일반적으로 GC의 스레드는 우선 순위가 낮습니다. JVM이 GC에 전화하기위한 많은 전략이 있습니다. 그들 중 일부는 메모리 사용이 특정 수준에 도달 할 때만 작동하기 시작합니다. 일부는 정기적으로 실행합니다. 일부는 GC를 원활하게 실행하고 일부는 인터럽트 방식으로 GC를 실행합니다. 그러나 일반적으로 말하면, 우리는 이것에 관심을 가질 필요가 없습니다. 일부 특정 상황에서 GC 실행은 응용 프로그램의 성능에 영향을 미칩니다. 예를 들어, 온라인 게임과 같은 실시간 웹 기반 시스템의 경우, 사용자는 GC가 갑자기 응용 프로그램 실행을 방해하고 쓰레기 수집을 수행하는 것을 원하지 않으면 GC의 매개 변수를 조정하여 GC가 세련된 방법으로 메모리를 자유롭게 해제하여 일련의 작은 단계로 분해 할 수 있습니다. Sun이 제공하는 핫스팟 JVM 은이 기능을 지원합니다.
또한 Java 메모리 누출의 전형적인 예를 제공합니다.
벡터 v = 새로운 벡터 (10); for (int i = 1; i <100; i ++) {object o = new Object (); v.add (o); o = null; }이 예에서는 객체 객체 사이클을 적용하고 적용된 객체를 벡터에 넣습니다. 참조 자체 만 해제하는 경우 벡터는 여전히 객체를 참조 하므로이 객체는 GC를 위해 재활용 할 수 없습니다. 따라서 벡터에 추가 된 후 벡터에서 물체를 삭제 해야하는 경우 가장 쉬운 방법은 벡터 객체를 NULL로 설정하는 것입니다.
자세한 자바의 메모리 누출
1. Java 메모리 재활용 메커니즘
모든 언어의 메모리 할당 방법에 관계없이 할당 된 메모리의 실제 주소를 반환해야합니다. 즉, 메모리 블록의 첫 번째 주소에 대한 포인터를 반환해야합니다. Java의 객체는 새 또는 반사 방법을 사용하여 작성됩니다. 이 물체의 생성은 힙에 할당됩니다. 모든 객체는 가비지 수집 메커니즘을 통해 Java Virtual Machine에 의해 수집됩니다. 객체를 올바르게 방출하기 위해 GC는 각 객체의 건강 상태를 모니터링하고 응용 프로그램, 인용, 인용, 할당 등을 모니터링합니다. Java는 지시 된 그래프 방법을 사용하여 메모리를 관리하여 객체를 실시간으로 달성 할 수 있는지 모니터링합니다. 도달하지 않으면 재활용되어 참조 루프의 문제를 제거 할 수 있습니다. Java 언어에는 메모리 공간이 쓰레기 수집 기준을 충족하는지 여부를 결정하는 두 가지 유형의 메모리 공간이 있습니다. 하나는 아래에 불려지지 않은 객체에 빈 값을 할당하는 것이며, 다른 하나는 객체에 새 값을 할당하여 메모리 공간을 재 할당하는 것입니다.
2. Java 메모리 누출의 원인
메모리 누출은 연속 쓸모없는 물체 (더 이상 사용되지 않는 객체) 또는 쓸모없는 물체의 메모리를 제 시간에 해제 할 수 없으므로 메모리 누출이라고하는 메모리 공간을 낭비합니다. 메모리 누출은 때때로 심각하지 않고 감지하기 쉽지 않으므로 개발자는 메모리 누출이 있다는 것을 알지 못하지만 때로는 매우 심각 할 수 있으며 메모리를 벗어날 수 있습니다.
Java 메모리 누출의 근본 원인은 무엇입니까? 장거리 사이클 객체가 짧은 수명주기 개체에 대한 참조를 보유하면 메모리 누출이 발생할 수 있습니다. 짧은 수명주기 객체는 더 이상 필요하지 않지만, 긴 수명주기에 대한 참조를 보유하고 있기 때문에 재활용 할 수 없습니다. 이것은 Java에서 메모리 누출이 발생하는 시나리오입니다. 주로 다음 범주가 있습니다.
1. 정적 수집 클래스는 메모리 누출을 유발합니다.
해시 맵, 벡터 등의 사용은 메모리 누출에서 발생할 가능성이 높습니다. 이러한 정적 변수의 수명주기는 응용 프로그램의 수명주기와 일치합니다. 그들이 참조하는 모든 객체는 벡터 등에 의해 참조되므로 해제 할 수 없습니다.
예를 들어
정적 벡터 v = 새로운 벡터 (10); for (int i = 1; i <100; i ++) {object o = new Object (); v.add (o); o = null;}이 예에서는 객체 객체가 적용되는 루프이고 적용된 객체는 벡터에 배치됩니다. 참조 자체가 릴리스 된 경우 (O = NULL) 벡터는 여전히 객체를 참조 하므로이 객체는 GC에 대해 재활용 할 수 없습니다. 따라서 벡터에 추가 된 후 벡터에서 물체를 삭제 해야하는 경우 가장 쉬운 방법은 벡터 객체를 NULL로 설정하는 것입니다.
2. 컬렉션의 객체 속성이 수정되면 remove () 메소드가 작동하지 않습니다.
예를 들어:
public static void main (string [] args) {set <person> set = new Hashset <person> (); Person P1 = New Person ( "Tang Monk", "Pwd1", "Pwd1", 25); pexer p2 = 새로운 사람 ( "Sun Wukong", "pwd2", 26); bajie ","pwd3 ", 27); set.add (p1); set.add (p2); set.add (p3); system.out.println ("+set.size ()+"요소!"); // 결과 : 총 3 개의 요소가 있습니다! p3.setage (2); // P3의 연령을 수정 하고이 시간에 P3 요소 변경에 해당하는 해시 코드 값을 수정합니다 .remove (p3); // 현재로서 제거하여 메모리 누출 세트를 유발합니다 .add (p3); // 다시 추가하면 System.out.println이 성공적으로 추가되었습니다 ( "+set.size ()+"요소! "); // 결과 : 총 4 개의 요소가 있습니다! for (person person : set) {System.out.println (person);}}3. 듣는 사람
Java 프로그래밍에서는 모두 청취자를 다루어야합니다. 일반적으로 많은 청취자가 응용 프로그램에 사용됩니다. 우리는 청취자를 추가하기 위해 addxxxlistener ()와 같은 제어 방법을 호출하지만, 종종 객체를 공개 할 때는 이러한 리스너를 삭제하여 메모리 누출 가능성을 높이는 것을 기억하지 못합니다.
4. 다양한 연결
예를 들어, Database Connection (DataSourse.getConnection ()), 네트워크 연결 (소켓) 및 IO 연결은 Close () 메소드를 호출하여 연결을 닫지 않는 한 GC에 의해 자동으로 재활용되지 않습니다. 결과 세트 및 명령문 객체는 명시 적으로 재활용 할 수 없지만 연결은 언제든지 자동으로 재활용 할 수 없으므로 연결을 명시 적으로 재활용해야합니다. 연결이 재활용되면 resultSet 및 명령문 개체는 즉시 널입니다. 그러나 연결 풀을 사용하는 경우 상황이 다릅니다. 연결을 명시 적으로 닫는 것 외에도 결과 세트 명령문 개체를 명시 적으로 닫아야합니다 (그중 하나를 닫고 다른 하나는 닫히게됩니다). 그렇지 않으면 많은 문장 객체가 해제되지 않아 메모리 누출이 발생하지 않습니다. 이 경우, 연결은 일반적으로 시도 및 마지막으로 릴리스됩니다.
5. 내부 클래스 및 외부 모듈에 대한 참조
내부 클래스에 대한 참조는 잊어 버리기가 비교적 쉽고 일단 출시되지 않으면 일련의 후속 클래스 객체가 공개되지 않을 수 있습니다. 또한 프로그래머는 외부 모듈에 대한 부주의 한 참조에도주의해야합니다. 예를 들어, 프로그래머 A는 모듈 A를 책임지고 다음과 같은 모듈 B의 메소드를 호출합니다.
공개 void registermsg (객체 B);
이런 종류의 전화에는 큰주의가 필요합니다. 객체가 전달되면 모듈 B가 객체에 대한 참조를 유지할 가능성이 큽니다. 현재 모듈 B가 참조를 제거하기 위해 해당 작업을 제공하는지 여부에주의를 기울여야합니다.
6. 싱글 톤 모드
싱글 톤 패턴을 잘못 사용하면 메모리 누출을 일으키는 일반적인 문제입니다. 싱글 톤 객체는 초기화 후 (정적 변수 형태) JVM의 전체 수명주기에 걸쳐 존재합니다. 싱글 톤 객체가 외부 참조를 보유하면이 객체는 JVM에 의해 정상적으로 재활용되지 않으므로 메모리 누출이 발생합니다. 다음 예를 고려하십시오.
class a {public a () {b.getinstance (). seta (this);} ....분명히 B는 객체 A에 대한 참조를 보유한 싱글 톤 패턴을 채택 하며이 클래스 A의 객체는 재활용되지 않습니다. A가 더 복잡한 개체 또는 수집 유형이라면 어떻게 될지 상상해보십시오.
안드로이드의 일반적인 메모리 누출 요약
컬렉션 클래스 누출
컬렉션 클래스에 요소를 추가하는 메소드 만 있고 해당 삭제 메커니즘이없는 경우 메모리가 점유됩니다. 이 컬렉션 클래스가 글로벌 변수 인 경우 (예 : 클래스의 정적 속성, 글로벌 맵 등, 즉 항상 정적 참조 또는 최종 지적이있는 경우) 해당 삭제 메커니즘이 없으므로 컬렉션이 차지하는 메모리가 증가하고 감소하지 않을 수 있습니다. 예를 들어, 위의 일반적인 예는 이러한 상황 중 하나입니다. 물론, 우리는 프로젝트에 그러한 2B 코드를 작성하지 않을 것이지만, 조심하지 않으면 여전히 쉬운 일이 있습니다. 예를 들어, 우리는 모두 해시 맵을 통해 캐시를하고 싶어 하므로이 상황에서 더 조심해야합니다.
싱글 톤으로 인한 메모리 누출
싱글 톤의 정적 특성은 애플리케이션의 수명주기만큼 수명주기를 만들기 때문에 부적절하게 사용하는 경우 메모리 누출을 유발하기 쉽습니다. 예를 들어 다음과 같은 일반적인 예입니다.
공개 클래스 appManager {private static appManager 인스턴스; 개인 컨텍스트 컨텍스트; 개인 appManager (컨텍스트 컨텍스트) {this.context = context;} public static appManager getInstance (컨텍스트 컨텍스트) {if (instance == null) {instance = new AppManager (context);} 인스턴스;}}.이것은 일반적인 싱글 톤 패턴입니다. 이 싱글 톤을 만들 때 컨텍스트를 통과해야 하므로이 컨텍스트의 수명주기의 길이는 중요합니다.
1. 응용 프로그램의 수명주기가 전체 애플리케이션의 수명주기이기 때문에 응용 프로그램의 컨텍스트가 전달되면 아무런 문제가 없습니다.
2.이 시점에서 활동 컨텍스트가 전달되는 경우,이 컨텍스트에 해당하는 활동이 종료 될 때, 컨텍스트에 대한 컨텍스트에 대한 참조가 싱글 톤 객체에 의해 유지되기 때문에, 수명주기는 전체 응용 수명주기와 같기 때문에 활동이 종료 될 때 메모리가 재활용되지 않아 누출이 발생합니다.
올바른 방법은 다음으로 변경해야합니다.
공개 클래스 AppManager {private static appmanager 인스턴스; 개인 컨텍스트 컨텍스트; 개인 appManager (컨텍스트 컨텍스트) {this.context = context.getApplicationContext (); // 컨텍스트 응용 프로그램} public static appManager getInstance (컨텐츠 == null) {컨텐츠 =}}}}}}또는 이런 식으로 작성하면 다음에서 컨텍스트를 전달할 필요조차 없습니다.
응용 프로그램에 정적 메소드를 추가하고 GetContext ()는 응용 프로그램의 컨텍스트를 반환합니다.
...
context = getApplicationContext (); .../*** 글로벌 컨텍스트 get global 컨텍스트*@return 글로벌 컨텍스트 객체*/public static context getContext () {return context;} public class appManager {private static appManager 인스턴스; 개인 컨텍스트; private appManager () {this.context = myApplication (// contextomtication.getContext); {if (instance == null) {instance = new AppManager ();} return instance;}}익명의 내부 클래스/비 정적 내부 클래스 및 비동기 스레드
비 정적 내부 클래스에서 정적 인스턴스를 생성하여 발생하는 메모리 누출
때때로 우리는 자주 활동을 시작할 수 있습니다. 동일한 데이터 리소스를 반복적으로 생성하지 않기 위해 이러한 글쓰기 방식이 발생할 수 있습니다.
공공 클래스 메인 랙션은 AppComPatactivity를 확장합니다 {private static testresource mresource = null; @overrideprotected void oncreate (Bundle SavedInstancestate) {super.oncreate (savedinstancestate); setcontentView (r.layout.activity_main); if (mmanager == null) ^ testresource ();} // ...} class testResource {// ...}}이것은 활동 내에서 비 정적 내부 클래스의 싱글 톤을 생성하며, 싱글 톤의 데이터는 활동이 시작될 때마다 사용됩니다. 반복적 인 자원 생성은 피하기 때문에이 글은 비 정적 내부 클래스가 기본적으로 외부 클래스에 대한 참조를 유지하고 비 정적 내부 클래스가 정적 인스턴스를 생성하고 인스턴스의 수명주기가 활동에 대한 참조를 항상 유지하는 한 인스턴스의 수명주기는 정상적으로 재활용 할 수없는 적용에 대한 참조 자원을 유지하는 한 인스턴스의 수명주기가 발생하기 때문에 메모리 누출을 유발합니다. 올바른 방법은 다음과 같습니다.
내부 클래스를 정적 내부 클래스로 설정하거나 내부 클래스를 추출하여 싱글 톤으로 캡슐화하십시오. 컨텍스트를 사용해야하는 경우 위의 권장 컨텍스트를 따라 응용 프로그램을 사용하십시오. 물론, 적용의 맥락은 전능하지 않으므로 무작위로 사용할 수 없습니다. 어떤 곳에서는 활동의 맥락을 사용해야합니다. 응용 프로그램, 서비스 및 활동의 컨텍스트의 응용 시나리오는 다음과 같습니다.
여기서 : No1은 응용 프로그램과 서비스가 활동을 시작할 수 있지만 새로운 작업 작업 대기열을 만들어야한다는 것을 의미합니다. 대화의 경우 활동에서만 만들 수 있습니다
익명의 내부 클래스
Android 개발은 종종 활동/조각/보기의 구현을 상속합니다. 현재 익명의 클래스를 사용하고 비동기 스레드로 보유하는 경우 조심하십시오. 척도가 없으면 누출로 이어질 것입니다.
공개 클래스 메인 액티브 확장 활동 {... runnable ref1 = new myrunable (); runnable ref2 = new Runnable () {@overridepublic void run () {}}; ...}ref1과 ref2의 차이점은 ref2가 익명 내부 클래스를 사용한다는 것입니다. 런타임에서 참조 된 메모리를 살펴 보겠습니다.
보시다시피, Ref1은 특별한 것이 아닙니다.
그러나 익명 클래스 ref2의 구현 객체에 추가 참조가 있습니다.
이 $ 0 참조는 MainActivity를 가리 킵니다. 즉, 현재 MainActivity 인스턴스는 ref2에 의해 유지됩니다. 이 기준이 비동기 실로 전달 되고이 스레드 와이 활동 수명주기가 일치하지 않으면 활동 누출이 발생합니다.
핸들러로 인한 메모리 누출
핸들러 사용으로 인한 메모리 누출 문제가 가장 일반적이라고 말해야합니다. ANR을 피하기 위해 기본 스레드에서 시간이 많이 걸리는 작업을 수행하지 않으며 처리기를 사용하여 네트워크 작업을 처리하거나 일부 요청 콜백 및 기타 API를 캡슐화합니다. 그러나 핸들러는 전능하지 않습니다. 핸들러의 코드가 표준화 된 방식으로 작성되면 메모리 누출이 발생할 수 있습니다. 또한, 우리는 핸들러, 메시지 및 메시지 queue가 모두 서로 관련이 있음을 알고 있습니다. 핸들러가 전송 한 메시지가 아직 처리되지 않은 경우, 전송 된 메시지와 핸들러 객체는 스레드 Messagequeue에서 보관합니다.
핸들러는 TLS (스레드 로컬 스토리지) 변수에 속하므로 수명주기와 활동은 일관성이 없습니다. 따라서이 구현 방법은 일반적으로보기 또는 활동의 수명주기와 일치하는지 확인하기가 어렵 기 때문에 올바른 릴리스를 쉽게 만듭니다.
예를 들어:
공개 클래스 샘플 리플 션은 활동을 확장합니다 {개인 최종 핸들러 mleakyhandler = new Handler () {@overridepublic void handlemessage (메시지 msg) {// ...}}@OverrideProtected void onCreate (Bundle SavedInstancestate) {SavedInstanceste (SavedInstancest); // 메시지); ming.mleakyHandler.postDelayed (new Runnable () {@overridepublic void run () {/ * ... */}}, 1000 * 60 * 10); // 이전 활동으로 돌아갑니다 .finish ();}}메시지 메시지가 지연된 10 분의 실행은 샘플 리플레이션에서 선언되며 mleakyhandler는 메시지 큐 Messagequeue로 밀어 넣습니다. 활동이 Finish ()에 의해 삭제되면, 작업의 실행을 지연시키는 메시지는 메인 스레드에 계속 존재할 것이므로 활동의 핸들러 참조를 보유하는 메인 스레드에 계속 존재하므로 Finish ()에 의해 삭제 된 활동은 재활용되지 않으므로 메모리 누출을 유발하지 않으므로 핸들러는 비 정적 내부 클래스이므로 외부 클래스에 참조를 보유하여 샘플링을 참조합니다.
수정 : 활동에 비 정적 내부 클래스를 사용하지 마십시오. 예를 들어, 핸들러를 위의 정적으로 선언하면 생존 기간은 활동의 수명주기와 관련이 없습니다. 동시에, 활동은 약한 참고 문헌을 통해 도입되어 활동을 문맥으로 직접 전달하지 않습니다. 다음 코드를 참조하십시오.
공개 클래스 샘플 리플 션은 활동을 확장합니다 {/*** 정적 내부 클래스의 인스턴스는 외부 클래스에 대한 암묵적*참조를 보유하지 않습니다.*/개인 정적 클래스 MyHandler는 핸들러를 확장합니다 {개인 최종 약점 <SampleActivity> mactivity; public myHandler (sampleActivity = and warlidic <sampoolic <amection)@ HandleMessage (Message Msg) {SampleActivity Activity = Mactivity.get (); if (activity! = null) {// ...}}} 개인 최종 MyHandler MHANDLER = NEW MYHANDLER (this);/*** 익명 클래스의 인스턴스는 외부 클래스에 대한 암묵적으로 참조하지 않습니다. {@overridepublic void run () {/ * ... */}};@atrementprotected void oncreate (Bundle SavedInstancestate) {super.oncreate (savedinstancestate); // 10 minutes.mhandler.postdelayed에 대한 메시지를 게시하고 실행 지연 activity.finish ();}}개요는 정적 내부 클래스 + 약점을 사용하는 것이 좋습니다. 마다 사용하기 전에 비워 지도록주의하십시오.
약점은 앞에서 언급되었으므로 여기에서는 여러 참조 유형의 Java 객체에 대해 간략하게 이야기 할 것입니다.
Java에는 강력한 참조, 소프트로 회의, 약점 및 phatomreference의 네 가지 범주의 참고 문헌이 있습니다.
안드로이드 애플리케이션 개발에서 메모리 오버플로를 방지하기 위해, 큰 메모리를 차지하고 선언주기가 긴 일부 객체를 처리 할 때, 소프트 참조 및 약한 참조 기술을 가능한 한 많이 사용할 수 있습니다.
소프트/약한 참조는 참조 큐 (참조 큐)과 함께 사용할 수 있습니다. 소프트 레퍼런스에 의해 언급 된 물체가 쓰레기 수집기에 의해 재활용되면 Java 가상 머신은 관련 참조 큐에 소프트 참조를 추가합니다. 이 대기열을 사용하면 재활용 된 소프트/약한 참조 목록을 알 수 있으므로 소프트/약한 참조에 실패한 버퍼를 지 웁니다.
응용 프로그램이 기본 아바타, 기본 게임 아이콘 등과 같은 많은 기본 이미지를 여러 곳에서 사용한다고 가정 해 봅시다. 매번 그림을 읽으면 파일을 읽는 데 하드웨어 작동이 필요하기 때문에 느리게 진행되므로 성능이 낮아집니다. 따라서 이미지 캐시를 고려하고 필요할 때 메모리에서 직접 읽습니다. 그러나 이미지는 많은 메모리 공간을 차지하고 많은 이미지가 많은 메모리를 필요로하기 때문에, 메모리 예외가 발생할 가능성이 높아질 수 있습니다. 현재이 문제를 피하기 위해 소프트/약한 참조 기술을 사용하는 것을 고려할 수 있습니다. 다음은 캐시의 프로토 타입입니다.
먼저 해시 맵을 정의하고 소프트 참조 객체를 저장하십시오.
개인 맵 <문자열, 소프트로 회의 <bitmap >> imagecache = new Hashmap <문자열, softreference <bitmap >> ();
비트 맵의 소프트 참조를 해시 맵에 저장하는 메소드를 정의해 봅시다.
소프트 참조를 사용한 후, 제외성 예외가 발생하기 전에, 이러한 캐시 된 이미지 리소스의 메모리 공간을 제거하여 메모리가 상한에 도달하지 못하고 충돌을 피할 수 없습니다.
Outfemory 예외의 발생을 피하려면 소프트 참조를 사용할 수 있습니다. 응용 프로그램의 성능에 더 관심이 있고 가능한 빨리 더 많은 메모리를 차지하는 일부 개체를 재활용하려면 약한 참조를 사용할 수 있습니다.
또한, 객체가 소프트 참조 또는 약한 기준을 위해 선택되었는지 여부를 결정하는 데 자주 사용되는지 여부를 결정할 수 있습니다. 물체를 자주 사용할 수 있다면 소프트 참조를 사용하십시오. 물체가 더 많이 사용되지 않으면 약한 참조와 함께 사용할 수 있습니다.
좋아, 주제로 계속 돌아가십시오. 앞에서 언급했듯이 정적 처리기 내부 클래스를 만들고 핸들러가 보유한 객체에 대한 약한 참조를 사용하여 핸들러가 보유한 객체를 재활용 중에 재활용 할 수 있습니다. 그러나 이것은 활동 누출을 피하기 때문에 루퍼 스레드의 메시지 대기열에 메시지가 계속 계류 중일 수 있으므로 활동 파괴 또는 정지 중에 메시지 대기일 메시지 큐에서 메시지를 제거해야합니다.
다음 방법은 메시지를 제거 할 수 있습니다.
공개 최종 공극 removecallbacks (runnable r); 공개 최종 void removecallbacks (runnable r, 객체 토큰); 공개 최종 무효 removecallbacksandmessages (객체 토큰); 공개 최종 무효 제거 제거 (int what); 공개 최종 void removemessages (int what, object object);
정적 멤버 변수를 사용하지 마십시오
멤버 변수가 정적으로 선언되면 수명주기가 전체 앱 프로세스 수명주기와 동일하다는 것을 알고 있습니다.
이로 인해 일련의 문제가 발생합니다. 앱 프로세스가 메모리 기수로 설계된 경우 앱이 백그라운드로 절단 되더라도 메모리 의이 부분이 해제되지 않습니다. 모바일 앱의 현재 메모리 관리 메커니즘에 따르면, 많은 양의 메모리를 설명하는 배경 프로세스가 먼저 재활용됩니다. 이 앱이 프로세스를 상호 보호하는 경우 앱이 백그라운드에서 자주 다시 시작됩니다. 전화가 개발에 참여한 앱을 설치하면 휴대 전화가 밤새 전원과 트래픽을 소비하며 사용자가 앱을 제거하거나 침묵시켜야합니다.
여기서 수정 사항은 다음과 같습니다.
수업 시작시 정적 멤버를 초기화하지 마십시오. 게으른 초기화를 고려할 수 있습니다.
건축 설계에서, 우리는 그것이 실제로이를 수행하고 피하려고 노력하는지 생각해야합니다. 아키텍처를 이와 같이 설계 해야하는 경우이 개체의 수명주기를 관리 할 책임이 있습니다.
Override Finalize ()를 피하십시오.
1. 최종 방법은 불확실한 시간에 실행되며 희소 한 자원을 해제하는 데 의존 할 수 없습니다. 불확실한 시간의 이유는 다음과 같습니다.
가상 머신이 GC를 호출하는 시간은 불확실합니다.
마무리 데몬 스레드가 예약되는 시간은 불확실합니다.
2. 최종 방법은 한 번만 실행됩니다. 객체가 부활 되더라도 최종 메소드가 실행되면 GC 인 경우 다시 실행되지 않습니다. 그 이유는 :
최종 메소드가 포함 된 객체는 새로울 때 가상 시스템에 의해 최종 참조를 생성하고 개체에 대한 참조를 생성합니다. 최종 메소드가 실행되면 객체에 해당하는 최종 참조가 해제됩니다. 현재 객체가 부활 되더라도 (즉, 강한 참조로 객체를 참조) GC입니다. 최종 참조가 더 이상 해당하지 않기 때문에 최종 방법은 실행되지 않습니다.
3. 최종 방법을 포함하는 객체는 공개되기 전에 최소 2 라운드의 GC를 거쳐야합니다.
불광으로 인한 메모리 누출
对于使用了BraodcastReceiver,ContentObserver,File,游标Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。
一些不良代码造成的内存压力
有些代码并不造成内存泄露,但是它们,或是对没使用的内存没进行有效及时的释放,或是没有有效的利用已有的对象而是频繁的申请新内存。
예를 들어:
Bitmap 没调用recycle()方法,对于Bitmap 对象在不使用时,我们应该先调用recycle() 释放内存,然后才它设置为null. 因为加载Bitmap 对象的内存空间,一部分是java 的,一部分C 的(因为Bitmap 分配的底层是通过JNI 调用的)。 而这个recyle() 就是针对C 部分的内存释放。
构造Adapter 时,没有使用缓存的convertView ,每次都在创建新的converView。这里推荐使用ViewHolder。
요약
对Activity 等组件的引用应该控制在Activity 的生命周期之内; 如果不能就考虑使用getApplicationContext 或者getApplication,以避免Activity 被外部长生命周期的对象引用而泄露。
尽量不要在静态变量或者静态内部类中使用非静态外部成员变量(包括context ),即使要使用,也要考虑适时把外部成员变量置空;也可以在内部类中使用弱引用来引用外部类的变量。
对于生命周期比Activity长的内部类对象,并且内部类中使用了外部类的成员变量,可以这样做避免内存泄漏:
将内部类改为静态内部类
静态内部类中使用弱引用来引用外部类的成员变量
Handler 的持有的引用对象最好使用弱引用,资源释放时也可以清空Handler 里面的消息。比如在Activity onStop 或者onDestroy 的时候,取消掉该Handler 对象的Message和Runnable.
在Java 的实现过程中,也要考虑其对象释放,最好的方法是在不使用某对象时,显式地将此对象赋值为null,比如使用完Bitmap 后先调用recycle(),再赋为null,清空对图片等资源有直接引用或者间接引用的数组(使用array.clear() ; array = null)等,最好遵循谁创建谁释放的原则。
正确关闭资源,对于使用了BraodcastReceiver,ContentObserver,File,游标Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销。
保持对对象生命周期的敏感,特别注意单例、静态对象、全局性集合等的生命周期。
The above is a summary of the causes of memory leaks in Java introduced to you by the editor and how to avoid memory leaks (super detailed version). 모든 사람에게 도움이되기를 바랍니다. 궁금한 점이 있으면 메시지를 남겨 주시면 편집자가 제 시간에 답장을 드리겠습니다. Wulin.com 웹 사이트를 지원해 주셔서 대단히 감사합니다!