天天看点

android studio 内存泄漏 profiler 内存泄漏

android studio 内存泄漏 profiler 内存泄漏

时间线缩放控件 控制底部时间戳显示间距。

android studio 内存泄漏 profiler 内存泄漏

当查看某个时间段内存情况时,可通过这个按钮回到实时预览。

android studio 内存泄漏 profiler 内存泄漏

紫色的点是显示 Activity 状态、用户输入 Event 和屏幕旋转 Event 的 Event 时间线

android studio 内存泄漏 profiler 内存泄漏

(1)**右侧上半部分:**TOTOL JAVA NATIVE GRAPHICS…

Java:从 Java 或 Kotlin 代码分配的对象内存。

Native:从 C 或 C++ 代码分配的对象内存。

Native:即使您的应用中不使用 C++,您也可能会看到此处使用的一些原生内存,因为 Android 框架使用原生内存代表您处理各种任务,如处理图像资源和其他图形时,即使您编写的代码采用 Java 或 Kotlin 语言。

Graphics:图形缓冲区队列向屏幕显示像素(包括 GL 表面、GL 纹理等等)所使用的内存。 (请注意,这是与 CPU 共享的内存,不是 GPU 专用内存。)

Stack: 您的应用中的原生堆栈和 Java 堆栈使用的内存。 这通常与您的应用运行多少线程有关。

Code:您的应用用于处理代码和资源(如 dex 字节码、已优化或已编译的 dex 码、.so 库和字体)的内存。

Other:您的应用使用的系统不确定如何分类的内存。

Allocated:您的应用分配的 Java/Kotlin 对象数。 它没有计入 C 或 C++ 中分配的对象。

**注意:**Android 7.1 及更低版本的设备时,此分配仅在 Memory Profiler 连接至您运行的应用时才开始计数。 因此,您开始分析之前分配的任何对象都不会被计入。 Android 8.0 附带一个设备内置分析工具,该工具可记录所有分配,

因此,在 Android 8.0 及更高版本上,此数字始终表示您的应用中待处理的 Java 对象总数。

(2)虚线:

虚线表示分配的对象数,如右侧的 y 轴所示。

(3)垃圾桶:

用于表示每个垃圾回收 Event 的图标。

android studio 内存泄漏 profiler 内存泄漏

红色圆圈时我自己画的:

Android7.0以下版本会有,8.0没有,这个的作用是记录内存分配情况的。7.0以前,需要开始和结束,8.0只要再时间线上拖动就可以了。(8.0 及更高版本附带设备内置分析工具,可持续跟踪您的应用分配。)

注意:*重点内容*

注:在 Android 7.1 及更低版本上,您最多可以记录 65535 个分配。 如果您的记录会话超出此限值,则记录中仅保存最新的 65535 个分配。 (在 Android 8.0 及更高版本中,则没有实际的限制。)

一、profiler能干嘛

查看分配哪些类型的对象以及它们使用多少空间。

每个分配的堆叠追踪,包括在哪个线程中。

对象在何时被取消分配(仅当使用运行 Android 8.0 或更高版本的设备时)。

android studio 内存泄漏 profiler 内存泄漏

点击内存展示下方的:arrange by package,找到我们自己的package。选中其中一个实例。

Arrange by class:基于类名称对所有分配进行分组。

Arrange by package:基于软件包名称对所有分配进行分组。

Arrange by callstack:将所有分配分组到其对应的调用堆栈。

InstanceView 展示的是我们实例的分配内存时间,和释放时间

CallStack 展示的是调用的堆栈,看过activity启动源码的同学是不是很熟悉呢。

官方文档说明:查看调用堆栈,7.0以下,需要手动dump。才能查看,我用的是8.0的系统。Dump Java heap 就可以。就是我画的那个红圈圈左边的图标。

操作步骤:先force GC点击一下垃圾桶,这样有利用于分析内存泄漏。

注意:如果您需要更精确地dump java heap。

import android.os.Debug;

调用Debug.dumpHprofData(“”)

或者在需要的地方:

Debug.startMethodTracing(“”);

//function…

Debug.stopMethodTracing();

都会在指定目录生成一个hprof文件。

分析内存泄漏:

Android 提供一个托管内存环境,当它确定您的应用不再使用某些对象时,垃圾回收器会将未使用的内存释放回堆中。系统都必须在某个时间点短暂地暂停您的代码。 大多数情况下,这些暂停难以察觉。

这是官方给出的一些内存方面的建议:包括如何减少apk大小,后台服务的优化

https://developer.android.com/topic/performance/memory

一些:需要注意的:

1.JobScheduler

2.nano protobufs:是一种与语言无关,平台无关的可扩展机制,由Google设计用于序列化结构化数据 - 类似于XML,但更小,更快,更简单

3.使用优化的数据容器

4.Dagger 2:Dagger不使用反射扫描您的应用程序代码。Dagger的静态编译时实现.

意味着它可以在Android应用程序中使用,而无需不必要的运行时成本或内存使用。

5.for创建过多对象。在ondraw中,创建paint或者bitmap。

贴出了维基百科中的工厂模式:

https://en.wikipedia.org/wiki/Factory_method_pattern

下面来说一些如何检测内存泄漏:

步骤:先手动force GC 画红圈圈最坐标的那个图标。点击dump java heap。

这时,studio会自动截取一定时间段的内存片段。可以点击保存按钮(下图左边那个绿箭头)将这些文件保存下来。扩展名.hprof。然后用studio File open 打开。扩展名别打错。

保存的.prof文件也可以用其他工具查看,mat,jhat。

方法:将 HPROF 文件从 Android 格式转换为 Java SE HPROF 格式。 使用 android_sdk/platform-tools/ hprof-conv

hprof-conv a/b/c/heap-original.hprof h/g/iheap-converted.hprof 指定对目录就可以了。一个输入一个输出。就可以用别的工具打开了。

1.我们可以通过Total Count(总实例数)和Heap Count(堆内存中实例数)

2.然后在下方的Reference Tree中我们可以看到当前这个实例对象持有的具体的对象,那么在这一部分我们怎么排查呢?我们主要需要关注的是 Dominating Size(当前指向的这个一条,在内存中占有的大小)值最大的前面几条,为什么呢?因为泄漏导致内存无法被释放值越大,存在泄漏的可能性越大。

3.在Analyzer中有一个功能就是 Detect Leaked Activities,点击绿色三角按钮运行后,可以帮我们分析出当前可能存在泄漏的Activity对象。

4.Reference Tree根据里边的堆栈信息一步步点开。找到有下面图片标记绿色箭头的那个标记。就看确定了。

android studio 内存泄漏 profiler 内存泄漏
android studio 内存泄漏 profiler 内存泄漏
android studio 内存泄漏 profiler 内存泄漏

这就是没被回收的实例,以及引用,还有调用的堆栈信息。右键有选项可以调到代码位置。

android studio 内存泄漏 profiler 内存泄漏

名词的说明:

在类列表中,您可以查看以下信息:

Heap Count:堆中的实例数。

Shallow Size:此堆中所有实例的总大小(以字节为单位)。

Retained Size:为此类的所有实例而保留的内存总大小(以字节为单位)。

在 Instance View 中,每个实例都包含以下信息:

Depth:从任意 GC 根到所选实例的最短 hop 数。理解为引用深度。

Shallow Size:此实例的大小。

Retained Size:此实例支配的内存大小(根据 dominator 树)。

Retained Size大体可以理解为:gc之后如果不泄漏能,释放的大小。

这是正常的activity

import android.content.Intent;
import android.os.Debug;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;

import java.io.IOException;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //try {
        //   Debug.dumpHprofData("");
        //} catch (IOException e) {
        //    e.printStackTrace();
        //}

        //Debug.startMethodTracing("");
        //Debug.stopMethodTracing();
    }

    public void btnClick(View v){
        startActivity(new Intent(this, SecondActivity.class));
    }

}
           

context被单利引用的:SecondActivity

public class SecondActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Singleton.getInstance(this);

        Button button = new Button(this);
        button.setLayoutParams(new FrameLayout.LayoutParams(, ));
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                finish();
            }
        });
        setContentView(button);

    }
}
           

官方给出的建议:

使用 Memory Profiler 时,可以跑 monkeyr unner 并尝试强制内存泄漏。 在应用中引发内存泄漏的一种方式是,先让其运行一段时间,然后再检查堆。 泄漏在堆中可能逐渐汇聚到分配顶部。 不过,泄漏越小,您越需要运行更长时间的应用才能看到泄漏。

您还可以通过以下方式之一触发内存泄漏:

将设备从纵向旋转为横向,然后在不同的 Activity 状态下反复操作多次。

旋转设备经常会导致应用泄漏 Activity、Context 或 View 对象,因为系统会重新创建 Activity,而如果您的应用在其他地方保持对这些对象之一的引用,系统将无法对其进行垃圾回收。

处于不同的 Activity 状态时,在您的应用与另一个应用之间切换(导航到主屏幕,然后返回到您的应用)。

内存泄漏情况及解决办法:

长时间引用 Activity、Context、View、Drawable 和其他对象,可能会保持对 Activity 或 Context 容器的引用。

可以保持 Activity 实例的非静态内部类,如 Runnable。

对象保持时间超出所需时间的缓存。

1.static变量引起的内存泄漏 
因为static变量的生命周期是在类加载时开始 类卸载时结束,也就是说static变量是在程序进程死亡时才释放,如果在static变量中 引用了Activity 那么 这个Activity由于被引用,便会随static变量的生命周期一样,一直无法被释放,造成内存泄漏。

解决办法: 
在Activity被静态变量引用时,使用 getApplicationContext 因为Application生命周期从程序开始到结束,和static变量的一样。

2.线程造成的内存泄漏 
类似于上述例子中的情况,线程执行时间很长,及时Activity跳出还会执行,因为线程或者Runnable是Acticvity内部类,因此握有Activity的实例(因为创建内部类必须依靠外部类),因此造成Activity无法释放。
AsyncTask 有线程池,问题更严重

解决办法: 
1.合理安排线程执行的时间,控制线程在Activity结束前结束。 
2.将内部类改为静态内部类,并使用弱引用WeakReference来保存Activity实例 因为弱引用 只要GC发现了 就会回收它 ,因此可尽快回收

3.BitMap占用过多内存 
bitmap的解析需要占用内存,但是内存只提供8M的空间给BitMap,如果图片过多,并且没有及时 recycle bitmap 那么就会造成内存溢出。

解决办法: 
及时recycle 压缩图片之后加载图片

4.资源未被及时关闭造成的内存泄漏 
比如一些Cursor 没有及时close 会保存有Activity的引用,导致内存泄漏

解决办法: 
在onDestory方法中及时 close即可

5.Handler的使用造成的内存泄漏 
由于在Handler的使用中,handler会发送message对象到 MessageQueue中 然后 Looper会轮询MessageQueue 然后取出Message执行,但是如果一个Message长时间没被取出执行,那么由于 Message中有 Handler的引用,而 Handler 一般来说也是内部类对象,Message引用 Handler ,Handler引用 Activity 这样 使得 Activity无法回收。

解决办法: 
依旧使用 静态内部类+弱引用的方式 可解决

这里做一个扩展:

dumpsys

可以提供有关系统服务的信息。

dumpsys -h 可以查看支持的功能

dumpsys -l 列出能dumpsys的 service

如:

dumpsys activity -h 可以继续查看dumpsys activity 能用的命令。可以分析activity跳转时,栈中的情况。举例说明。

以上都是在adb shell 下运行。

不知道写的好不好,都是自己学的东西。希望我们大家可以多沟通沟通有什么介意。可以写在下面。一起进步。