English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Android Studio+MAT 실전 메모리 누수

Android에서 메모리 유출은 주의하지 않으면 쉽게 발생할 수 있습니다. 특히 Activity에서는 더 쉽게 발생합니다. 저는 어떻게 메모리 유출을 검색하는지 설명하겠습니다.

먼저 메모리 유출이 무엇인지 설명해 보겠습니다.

메모리 유출은 이미 사용되지 않은 객체가 메모리에 남아 있으며, 휴지 회수 기계가 그들을 수거할 수 없기 때문에, 지속적으로 메모리를 소비하고, 결국 프로그램 성능이 저하됩니다.
Android 가상기기에서는 루트 노드 탐색 알고리즘을 사용하여 루트 노드를 열거하고, 그 노드가 휴지 여부를 판단합니다. 가상기기는 GC Roots에서 시작하여 탐색하며, 노드가 GC Roots에 도달할 수 있는 경로를 찾지 못하면, 즉 GC Roots와 연결되지 않으면, 그 참조는 유효하지 않으며 수거될 수 있습니다. 메모리 유출은 불좋은 호출로 인해 무용 객체와 GC Roots가 연결되어 수거되지 않는 경우 발생합니다.

内存泄漏이 무엇인지 알게 되면, 자연스럽게 방지법도 알 수 있습니다. 우리가 코드를 작성할 때는 무용 객체에 장기간의 참조를 유지하는 것을 최대한 주의해야 합니다. 이야기는 간단하지만, 충분한 경험 없이는 달성하기 어렵습니다. 따라서 메모리 유출은 쉽게 발생할 수 있습니다. 완전히 방지하기 어려우면, 프로그램에서 발생하는 메모리 유출을 발견하고 수정할 수 있어야 합니다.
그럼内存泄漏是怎么发现的呢?

메모리 유출 검색:

예를 들어 아래와 같은 코드가 있습니다:

public class MainActivity extends AppCompatActivity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    String string = new String();
  }
  public void click(View view){
    Intent intent = new Intent();
    intent.setClass(getApplicationContext(), SecondActivity.class);
    startActivity(intent);
  }
}
public class SecondActivity extends AppCompatActivity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_second);
    Runnable runnable = new Runnable() {
      @Override
      public void run() {
        try {
          Thread.sleep(8000000L);
        catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    };
    new Thread(runnable).start();
  }
}

 

매번 이 Activity로 이동할 때마다 스레드가 호출되며, 그 스레드는 Runnable의 run 메서드를 실행합니다. Runnable이 익명 내부 객체이기 때문에 SecondActivity의 참조를 가지고 있어서, 두 Activity가 간단하게 MainActivity에서 SecondActivity로 이동할 수 있습니다. 아래에서 MainActivity에서 SecondActivity로 이동하고, 그 다음 SecondActivity에서 MainActivity로 돌아옵니다. 이렇게 반복합니다.5번, 마지막으로 MainActivity로 돌아옵니다. 일반적으로 SecondActivity에서 MainActivity로 돌아온 후, SecondActivity는 파괴되고 재활성화되어야 합니다. 그러나 실제로는 그렇지 않을 수 있습니다.

이제 메모리 누수가 발생했는지 판단하기 위해 도구를 사용해야 합니다! 아래는 두 가지 방법입니다

1.MAT 도구를 사용하여 검색합니다

먼저 AS의 Android Device Monitor 도구를 열어야 합니다. 자세한 위치는 아래 그림과 같습니다:


오픈하면 아래와 같은 인터페이스가 나타납니다


먼저 검사할 애플리케이션의 패키지 이름을 선택한 후, 아래 그림에 표시된 곳을 클릭하면 프로그램 패키지 이름 뒤에 아이콘을 표시합니다


다음으로는 우리의 앱을 왕복으로 전환해야 합니다5번.

그 후에는 아래 그림에 표시된 아이콘을 클릭하여 hprof 파일을 내보내고 분석할 수 있습니다

출력 파일은 아래와 같이 표시됩니다:

hprof 파일을 얻으면 MAT 도구를 사용하여 분석할 수 있습니다.

MAT 도구를 엽니다

없다면 아래 URL에서 다운로드할 수 있습니다

MAT 도구 다운로드 주소

인터페이스는 아래와 같이 표시됩니다:

이전에 내보낸 hprof 파일을 열면 예상치 못한 오류가 발생할 수 있습니다

MAT는 java 프로그램의 hprof 파일을 분석하는 도구이며, Android에서 내보낸 hprof와는 형식이 다르기 때문에, 변환된 hprof 파일을 필요로 합니다. SDK에서 제공하는 hprof 변환 도구-conv.exe는 아래 위치에 있습니다


그 다음에는 해당 경로로 cd를 입력하여 명령어를 실행하여 hprof 파일을 변환하면 됩니다. 아래 그림과 같이 표시됩니다


그 중 hprof-conv 명령어 이렇게 사용합니다

hprof-conv 소스 파일 출력 파일

예를 들어 hprof-conv E:\aaa.hprof E:\output.hprof

이것은 aaa.hprof를 output.hprof로 변환하여 출력하는 것입니다. output.hprof는 변환한 파일입니다. 그림에서 mat2.hprof는 변환한 파일입니다.

그 다음에는 MAT 도구를 사용하여 변환된 mat 파일을 엽니다.2.hprof 파일을 열면 오류가 발생하지 않습니다. 아래와 같이 표시됩니다:


그 후에는 현재 메모리에 존재하는 객체를 확인할 수 있습니다. 우리가 일반적으로 Activity에서 메모리 누수가 발생하기 때문에, Activity만을 검색하면 됩니다.

아래 그림에 표시된 QQL 아이콘을 클릭한 후 select를 입력하십시오 * from instanceof android.app.Activity

SQL 문장과 유사하게 Activity와 관련된 정보를 찾아 클릭한 후, 아래와 같이 표시된다:

다음으로, 아래 필터된 Activity 정보를 볼 수 있습니다

위 그림과 같이, 메모리에 여전히 존재하는 6개의 SecondActivity 인스턴스가 있지만, 모두 퇴장하려고 하는 것이며, 이는 메모리 누수가 발생한 것을 의미합니다

Shallow size와 Retained size 두 가지 속성이 있습니다

Shallow Size
객체 자신이 사용하는 메모리 크기는 참조하는 객체를 포함하지 않습니다. 배열 타입이 아닌 객체의 경우, 크기는 객체와 그 모든 멤버 변수의 크기 총합입니다.
물론 여기에는 Java 언어 특성의 데이터 저장 단위도 포함됩니다. 배열 타입의 객체의 크기는 배열 요소 객체의 크기 총합입니다.
Retained Size
Retained Size=현재 객체 크기+현재 객체가 직접적이거나 간접적으로 참조할 수 있는 객체의 크기 총합。(간접 참조의 의미: A->B->C, C는 간접 참조)
그러나, 해제할 때는 GC Roots로 직접적이거나 간접적으로 참조되는 객체를 제외해야 합니다. 그들은 일시적으로 Garbage로 간주되지 않습니다.

다음으로, SecondActivity를 오른쪽 클릭하십시오


모든 참조와 함께 선택하십시오

다음 그림과 같은 페이지를 엽니다

다음 그림의 페이지를 확인하십시오

이를 참조하는 this0을 볼 수 있습니다.Activity그리고this0은 내부 클래스를 의미하며, 즉 Activity를 참조하는 내부 클래스이며 this$0이 target에 의해 참조된다는 것을 의미합니다. target은 스레드입니다. 이유를 찾았습니다. 메모리 누수의 원인은 Activity가 내부 클래스에 의해 참조되고 내부 클래스가 스레드에 의해 사용되어서 해제할 수 없다는 것입니다. 따라서 이 클래스의 코드를 확인하겠습니다.

public class SecondActivity extends AppCompatActivity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_second);
    Runnable runnable = new Runnable() {
      @Override
      public void run() {
        try {
          Thread.sleep(8000000L);
        catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    };
    new Thread(runnable).start();
  }
}
물론,

물론, SecondActivity에서 Runnable 내부 클래스 객체가 있으며, 이를 스레드가 사용하며, 스레드는 실행해야 합니다8000초. 따라서 SecondActivity 객체가 참조되어 해제되지 못하고, 메모리 초과가 발생했습니다.

이러한 메모리 초과를 해결하기 위해, Activity가 벗어났을 때 스레드를 종료하거나(그러나 좋지 않게 종료됩니다.), 스레드의 실행 시간을 잘 관리하면 됩니다.

이렇게 우리는 이 프로그램에서 메모리 초과를 찾았습니다.

2Android Studio의 Monitor Memory를 통해 메모리 초과를 찾습니다

그리고 위의 프로그램을 사용하여, 간단히 설명하겠습니다.

먼저, 스마트폰에서 프로그램을 실행하고 AS의 Monitor界面를 열어 Memory 이미지를 확인하십시오

그림에서 클릭하십시오1위치 아이콘(위치 아이콘)


그림에서 클릭하십시오2위치 아이콘을 통해 hprof 파일을 확인할 수 있습니다

왼쪽은 메모리에 있는 객체이며, 그 안에서 Activity를 찾아서 우리가 이미 해제한 Activity가 있는지 확인합니다. 우리가 기대하는 이미 해제된 Activity가 나타나면, 클릭하면 오른쪽에 그의 총 개수가 표시되고, 오른쪽의 어떤 것을 클릭하면 그의 GC Roots의 트리 관계도 표시됩니다. 관계도를 통해 메모리 누수가 발생하는 위치를 찾을 수 있습니다(첫 번째 방식과 유사하게)

이렇게 메모리 누수의 위치를 찾았습니다.

Android에서 메모리 누수가 발생하는 원인은 대략 다음과 같이 분류됩니다:

1정적 변수로 인한 메모리 누수

정적 변수의 라이프사이클은 클래스가 로드될 때 시작되고 클래스가 언로드될 때 끝납니다. 즉, 정적 변수는 프로그램 프로세스가 죽을 때까지 해제되지 않습니다. 정적 변수에서 Activity를 참조하는 경우, 이 Activity는 참조되기 때문에 정적 변수와 같은 라이프사이클을 가지게 되어, 해제되지 못하게 되어 메모리 누수가 발생합니다.

해결책:

Activity가 정적 변수에 의해 참조될 때, getApplicationContext를 사용하십시오. Application의 라이프사이클은 프로그램이 시작되고 끝날 때까지, 정적 변수와 같습니다.

2스레드로 인한 메모리 누수

위의 예제와 유사한 경우, 스레드가 실행되는 시간이 매우 길어서 Activity가 벗어났음에도 불구하고 계속 실행됩니다. 이는 스레드나 Runnable이 Activity 내부 클래스이기 때문입니다. 따라서 Activity의 인스턴스를 가지고 있습니다(내부 클래스를 생성하기 위해서는 외부 클래스에 의존해야 하기 때문에). 이는 Activity가 해제되지 못하게 만듭니다.

AsyncTask는 스레드 풀을 가지고 있으며, 문제가 더 심각합니다

해결책:

1.스레드가 Activity가 끝나기 전에 항상 종료되도록 시간을合理安排합니다

2.내부 클래스를 정적 내부 클래스로 변경하고 Activity 인스턴스를 저장하기 위해 WeakReference를 사용합니다. 왜냐하면 약한 참조는 GC가 발견하면 즉시 재활용되기 때문에 빠르게 재활용할 수 있습니다

3.BitMap이 많은 메모리를 차지합니다

bitmap의 해석은 메모리를 차지하지만, 메모리는 제공됩니다8BitMap에 M의 공간을 할당하면, 이미지가 많고 BitMap을 즉시 recyle하지 않으면 메모리 초과가 발생할 수 있습니다

해결책:

이미지를 로드하기 전에 이미지를 줄이고 recyle하여 메모리를 효율적으로 사용할 수 있습니다

4.자원이 즉시 close되지 않아 메모리 누수가 발생합니다

예를 들어, Cursor가 시간이 지나도 close되지 않으면 Activity의 참조를 저장하며, 메모리 누수가 발생합니다

해결책:

onDestroy 메서드에서 즉시 close하여 해결할 수 있습니다

5.Handler 사용으로 인한 메모리 누수

Handler 사용 시, Handler가 Message 객체를 MessageQueue에 전송하고 Looper가 MessageQueue를 루프하며 Message를 꺼내 실행합니다. 그러나 Message가 오랫동안 꺼내지 않으면 Message에 Handler의 참조가 있으며, Handler는 일반적으로 내부 클래스 객체이므로 Message가 Handler를 참조하고, Handler가 Activity를 참조하여 Activity가 재활용되지 않습니다.

해결책:

여전히 정적 내부 클래스를 사용합니다+약한 참조 방식으로 해결할 수 있습니다

중간에 객체가 제거되지 않았거나, 등록된 객체가 해제되지 않았을 때, 코드의 부하 문제도 메모리 누수가 발생할 수 있습니다. 그러나 위의 몇 가지 해결책을 사용하면 대부분 해결할 수 있습니다.

좋아하는 것