内存泄漏是指再也不被使用的对象的内存不能被GC回收,同时频繁的GC会形成卡顿。Android系统为每一个应用程序分配的内存是有限的,若是应用中内存泄漏较多,就很容易形成OOMjava
Java 程序运行时的内存分配策略有三种,分别是静态分配,栈式分配,和堆式分配,对应的,三种存储策略使用的内存空间主要分别是静态存储区(也称方法区)、栈区和堆区。数据库
• 静态存储区(方法区):主要存放静态数据、全局 static数据和常量。这块内存在程序编译时就已经分配好,而且在程序整个运行期间都存在。缓存
• 栈区 :当方法被执行时,方法体内的局部变量都在栈上建立,并在方法执行结束时这些局部变量所持有的内存将会自动被释放。由于栈内存分配运算内置于处理器的指令集中,效率很高,可是分配的内存容量有限。app
• 堆区 : 又称动态内存分配,一般就是指在程序运行时直接 new 出来的内存。这部份内存在不使用时将会由 Java 垃圾回收器来负责回收。框架
static修饰成员变量时,那么该变量就属于该类,而不是该类的实例。若是你这样作,那意味此成员变量的生命周期,会被拉长到与整个app进程生命周期一致。因此用static修饰的变量,它的生命周期是很长的,若是用它来引用一些资源耗费过多的对象,就容易出现内存泄露的状况dom
使用static静态变量,应注意:
第一,应该尽可能避免static成员变量引用资源耗费过多对象。
第二,在static变量引用对象的时候,在被引用对象再也不使用的时候,应及时释放引用,作置null操做 。ide
单例模式只容许应用程序存在一个实例对象,而且这个实例对象的生命周期和应用程序的生命周期同样长,若是单例对象中拥有另外一个对象的引用的话,这个被引用的对象就不能被及时回收。(使用弱引用)工具
若是传入的context是Activity将会形成内存泄漏,若是是Application就不至于内存泄漏,由于Application的生命周期长。布局
public class AppManager {
private static AppManager instance;
private Context context;
private AppManager(Context context) {
this.context = context;
}
public static AppManager getInstance(Context context) {
if (instance != null) {
instance = new AppManager(context);
}
return instance;
}
}
复制代码
长生命周期形成的内存泄漏,如Application,由于它的生命周期和整个应用的生命周期同样长:post
public class MyApplication extends Application {
private Activity currenActivity;
public void setCurrenActivity(Activity currenActivity){
this.currenActivity = currenActivity;
}
}
复制代码
public class TestActivity4 extends Activity{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MyApplication application = (MyApplication) getApplication();
application.setCurrenActivity(this);
}
}
复制代码
public class TestActivity2 extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
Log.i("TestActivity2","启动了");
}
},1000000);
}
}
复制代码
若是启动Activity打开后,而后当即关闭了,这种状况下就会发生内存泄漏。咱们知道,Handler、Message、MessageQueue是相互关联在一块儿的,Handler经过发送消息Message与主线程进行交互,若是Handler发送的消息Message还没有被处理,该Message及发送它的Handler对象将被MessageQueue一直持有,这样就可能会致使Message没法被回收。本例中Runable为被内存泄漏的消息,又由于匿名内部类会持有外部类的引用,全部形成Activity的泄漏。不过本例中由于只会延迟一秒执行消息,因此这种内存泄漏的危害不是很大。对于Handler的使用,能够以以下方式:
public class TestActivity2 extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
new NewHandler(this).sendEmptyMessageDelayed(0,1000000);
}
private static class NewHandler extends Handler{
private WeakReference<Activity> weakActivity;
NewHandler(Activity activity){
weakActivity = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
Activity activity = weakActivity.get();
if(activity!=null){
Log.i("TestActivity2",activity.getClass().getSimpleName()+"启动");
}
}
}
}
复制代码
以静态类的方式定义Handler,这样就不会直接持有Activity的引用,而Activity由弱引用的方式持有,当一个对象仅仅只有弱引用,那它和没有引用是同样的,当GC启动时,它将会当即被回收。
因此:Handler类须要声明为static,不然会发生泄漏。 缘由是Message进入消息队列时,会持有对目标Handler的引用,若是Handler是内部类,内部类还会持有对外部类的引用, 为了不对外部类的泄漏,Handler应该声明为静态嵌套类,持有对外部类的弱引用。
对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,不然这些资源将不会被回收,形成内存泄漏。
非静态内部类中建立了一个静态实例,致使该实例的生命周期和应用ClassLoader级别,又由于该静态实例又会隐式持有其外部类的引用,因此致使其外部类没法正常释放,出现了泄漏问题。
当您首次打开 Memory Profiler 时,您将看到一条表示应用内存使用量的详细时间线,并可访问用于强制执行垃圾回收、捕捉堆转储和记录内存分配的各类工具。
您在 Memory Profiler(图 2)顶部看到的数字取决于您的应用根据 Android 系统机制所提交的全部私有内存页面数。 此计数不包含与系统或其余应用共享的页面。
内存计数中的类别以下所示:
• Java:从 Java 或 Kotlin 代码分配的对象内存。
• Native:从 C 或 C++ 代码分配的对象内存。 即便您的应用中不使用 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 对象总数。 与之前的 Android Monitor 工具中的内存计数相比,新的 Memory Profiler 以不一样的方式记录您的内存,所以,您的内存使用量如今看上去可能会更高些。 Memory Profiler 监控的类别更多,这会增长总的内存使用量,但若是您仅关心 Java 堆内存,则“Java”项的数字应与之前工具中的数值类似。 然而,Java 数字可能与您在 Android Monitor 中看到的数字并不是彻底相同,这是由于应用的 Java 堆是从 Zygote 启动的,而新数字则计入了为它分配的全部物理内存页面。 所以,它能够准确反映您的应用实际使用了多少物理内存。 注:目前,Memory Profiler 还会显示应用中的一些误报的原生内存使用量,而这些内存其实是分析工具使用的。 对于大约 100000 个对象,最多会使报告的内存使用量增长 10MB。 在这些工具的将来版本中,这些数字将从您的数据中过滤掉。
堆转储显示在您捕获堆转储时您的应用中哪些对象正在使用内存。 特别是在长时间的用户会话后,堆转储会显示您认为不该再位于内存中却仍在内存中的对象,从而帮助识别内存泄漏。 在捕获堆转储后,您能够查看如下信息:
• 您的应用已分配哪些类型的对象,以及每一个类型分配多少。
• 每一个对象正在使用多少内存。
• 在代码中的何处仍在引用每一个对象。
• 对象所分配到的调用堆栈。 (目前,若是您在记录分配时捕获堆转储,则只有在 Android 7.1 及更低版本中,堆转储才能使用调用堆栈。)
要捕获堆转储,在 Memory Profiler 工具栏中点击 Dump Java heap
在类列表中,您能够查看如下信息: • Heap Count:堆中的实例数。 • Shallow Size:此堆中全部实例的总大小(以字节为单位)。 • Retained Size:为此类的全部实例而保留的内存总大小(以字节为单位)。 在类列表顶部,您可使用左侧下拉列表在如下堆转储之间进行切换: • Default heap:系统未指定堆时。 • App heap:您的应用在其中分配内存的主堆。 • Image heap:系统启动映像,包含启动期间预加载的类。 此处的分配保证毫不会移动或消失。 • Zygote heap:写时复制堆,其中的应用进程是从 Android 系统中派生的。 默认状况下,此堆中的对象列表按类名称排列。 您可使用其余下拉列表在如下排列方式之间进行切换: • Arrange by class:基于类名称对全部分配进行分组。 • Arrange by package:基于软件包名称对全部分配进行分组。 • Arrange by callstack:将全部分配分组到其对应的调用堆栈。 此选项仅在记录分配期间捕获堆转储时才有效。 即便如此,堆中的对象也极可能是在您开始记录以前分配的,所以这些分配会首先显示,且只按类名称列出。 默认状况下,此列表按 Retained Size 列排序。 您能够点击任意列标题以更改列表的排序方式。 在 Instance View 中,每一个实例都包含如下信息: • Depth:从任意 GC 根到所选实例的最短 hop 数。 • Shallow Size:此实例的大小。 • Retained Size:此实例支配的内存大小(根据 dominator 树)。