时间线缩放控件 控制底部时间戳显示间距。
当查看某个时间段内存情况时,可通过这个按钮回到实时预览。
紫色的点是显示 Activity 状态、用户输入 Event 和屏幕旋转 Event 的 Event 时间线
(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 的图标。
红色圆圈时我自己画的:
Android7.0以下版本会有,8.0没有,这个的作用是记录内存分配情况的。7.0以前,需要开始和结束,8.0只要再时间线上拖动就可以了。(8.0 及更高版本附带设备内置分析工具,可持续跟踪您的应用分配。)
注意:*重点内容*
注:在 Android 7.1 及更低版本上,您最多可以记录 65535 个分配。 如果您的记录会话超出此限值,则记录中仅保存最新的 65535 个分配。 (在 Android 8.0 及更高版本中,则没有实际的限制。)
一、profiler能干嘛
查看分配哪些类型的对象以及它们使用多少空间。
每个分配的堆叠追踪,包括在哪个线程中。
对象在何时被取消分配(仅当使用运行 Android 8.0 或更高版本的设备时)。
点击内存展示下方的: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根据里边的堆栈信息一步步点开。找到有下面图片标记绿色箭头的那个标记。就看确定了。
这就是没被回收的实例,以及引用,还有调用的堆栈信息。右键有选项可以调到代码位置。
名词的说明:
在类列表中,您可以查看以下信息:
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(140, 140));
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 下运行。
不知道写的好不好,都是自己学的东西。希望我们大家可以多沟通沟通有什么介意。可以写在下面。一起进步。