首页 > 技术文章 > android内存分配及泄露

kma-3 2017-06-27 15:54 原文

Java 内存分配策略

Java 程序运行时的内存分配策略有三种,分别是静态分配,栈式分配,和堆式分配,对应的,三种存储策略使用的内存空间主要分别是静态存储区(也称方法区)、栈区和堆区。

•静态存储区(方法区):主要存放静态数据、全局 static 数据和常量。这块内存在程序编译时就已经分配好,并且在程序整个运行期间都存在,生命周期和整个应用相同。

•栈区 :当方法被执行时,方法体内的局部变量都在栈上创建,并在方法执行结束时这些局部变量所持有的内存将会自动被释放。因为栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。在栈区一般存储一些基本的数据类型(int, short, long, byte, float, double, boolean, char),类方法,对象地址,常量等等。

•堆区 :又称动态内存分配,通常就是指在程序运行时直接 new 出来的内存。这部分内存在不使用时将会由 Java 垃圾回收器来负责回收,缺点是要在运行时分配内存,存取速度慢。         通常存储一些通过new来创建的对象,但是该对象的地址一般保存在栈中.

查看内存使用情况

可以使用AS的monitor实时观察app运行是内存耗费情况。 使用adb shell meminfo $包名或 $进程号 来显示该应用的内存耗费情况

GC的原理:

Java的内存管理就是对象的分配和释放问题。在 Java 中,程序员需要通过关键字 new 为每个对象申请内存空间 (基本类型除外),所有的对象都在堆 (Heap)中分配空间。另外,对象的释放是由 GC 决定和执行的。在 Java 中,内存的分配是由程序完成的,而内存的释放是由 GC 完成的,这种收支两条线的方法确实简化了程序员的工作。但同时,它也加重了JVM的工作。这也是 Java 程序运行速度较慢的原因之一。因为,GC 为了能够正确释放对象,GC 必须监控每一个对象的运行状态,包括对象的申请、引用、被引用、赋值等,GC 都需要进行监控。 监视对象状态是为了更加准确地、及时地释放对象,而释放对象的根本原则就是该对象不再被引用。 为了更好理解 GC 的工作原理,我们可以将对象考虑为有向图的顶点,将引用关系考虑为图的有向边,有向边从引用者指向被引对象。另外,每个线程对象可以作为一个图的起始顶点,例如大多程序从 main 进程开始执行,那么该图就是以 main 进程顶点开始的一棵根树。在这个有向图中,根顶点可达的对象都是有效对象,GC将不回收这些对象。如果某个对象 (连通子图)与这个根顶点不可达(注意,该图为有向图),那么我们认为这个(这些)对象不再被引用,可以被 GC 回收。

什么是Java中的内存泄露:

在上面描述了GC的原理,只要从根节点出发便利有向图,只要不可达的节点都是GC对象。然而在现实的操作中,有些对象通过根节点可达,但是这些对象是无用的 ,即程序不再会使用该对象。如果出现上述两种情况就可以判定内存泄露了。

检测内存泄露的工具:

leakcanary 一个开源的工具,在检测对象被destory后一一检查其内部的对象是否还有未释放的引用,如果有则提示泄露

常见的内存泄露:

1、静态集合类引起内存泄漏: 像HashMap、Vector等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,他们所引用的所有的对象Object也不能被释放,因为他们也将一直被Vector等引用着。

Vector v = new Vector(10); 
for (int i = 1; i < 100; i++) {     
    Object o = new Object();     
    v.add(o);    
    o = null;   
}

 

2、当集合里面的对象属性被修改后,再调用remove()方法时不起作用。

public static void main(String[] args)
{
    Set set = new HashSet();
    Person p1 = new Person("唐僧","pwd1",25);
    Person p2 = new Person("孙悟空","pwd2",26);
    Person p3 = new Person("猪八戒","pwd3",27);
    set.add(p1);
    set.add(p2);
    set.add(p3);
    System.out.println("总共有:"+set.size()+" 个元素!"); 
    p3.setAge(2); 
    set.remove(p3); 
    set.add(p3); 
    System.out.println("总共有:"+set.size()+" 个元素!"); 
   for (Person person : set)
   {
        System.out.println(person);
   }
}

 

3、监听器 在java 编程中,我们都需要和监听器打交道,通常一个应用当中会用到很多监听器,我们会调用一个控件的诸如addXXXListener()等方法来增加监听器,但往往在释放对象的时候却没有记住去删除这些监听器,从而增加了内存泄漏的机会。

4、各种连接 比如数据库连接(dataSourse.getConnection()),网络连接(socket)和io连接,除非其显式的调用了其close()方法将其连接关闭,否则是不会自动被GC 回收的。对于Resultset 和Statement 对象可以不进行显式回收,但Connection 一定要显式回收,因为Connection 在任何时候都无法自动回收,而Connection一旦回收,Resultset 和Statement 对象就会立即为NULL。但是如果使用连接池,情况就不一样了,除了要显式地关闭连接,还必须显式地关闭Resultset Statement 对象(关闭其中一个,另外一个也会关闭),否则就会造成大量的Statement 对象无法释放,从而引起内存泄漏。这种情况下一般都会在try里面去的连接,在finally里面释放连接。

5、内部类和外部模块的引用

private static Object inner;
   void createInnerClass() {
    class InnerClass {
    }
    inner = new InnerClass();
}

 

6、单例模式 不正确使用单例模式是引起内存泄漏的一个常见问题,单例对象在初始化后将在JVM的整个生命周期中存在(以静态变量的方式),如果单例对象持有外部的引用,那么这个对象将不能被JVM正常回收,导致内存泄漏


7、一些静态的的变量持有局部对象
在程序中由于静态变量是和应用的生存周期保持一致的,但是在类中通过new一些局部对象赋值给这些静态变量时,当当前类销毁时这些局部对象也一样会
跟着销毁,但是由于静态变量持有这些对象的引用,跟应用的生存周期保持一致,所以这种情况下会导致内存泄漏。
常见的一些赋值给静态变量的对象:
通过findViewById获取的view赋值给static view;
new一个对象赋值给static 变量
static activity 的赋值

 8.还有一些异步操作导致的泄露

在Android中有很多种异步操作,handler操作;new一个线程Runnable,asyncTask等等,通过这些方法开启一个线程工作,如果有时候线程耗时很长而当前activity已经被destory了,所以这些线程还一直存在但是已经是没什么必要了。

如何避免内存泄露:

1、对于一些静态的变量,可以通过弱引用赋予其对象,这样在进行GC的时候,弱引用的对象一样能够被回收。

 private static WeakReference<MainActivity> activityReference;
 void setStaticActivity() {
     activityReference = new WeakReference<MainActivity>(this);
}

这种情况同样适用于其他类似的static变量。

2、对于一些匿名类导致的泄露可以使用静态内部类来代替,这样由于该类是静态的跟应用拥有相同的生命周期

private static class NimbleTask extends AsyncTask<Void, Void, Void> {
    @Override protected Void doInBackground(Void... params) {
        while(true);
    }
}

void startAsyncTask() {
    new NimbleTask().execute();
}

对于开启的线程也可以如果在activity被destory的时候,不需要进行执行可以在周期函数destory中将该thread进行中断。

private Thread thread;

@Override
public void onDestroy() {
    super.onDestroy();
    if (thread != null) {
        thread.interrupt();
    }
}

void spawnThread() {
    thread = new Thread() {
        @Override public void run() {
            while (!isInterrupted()) {
            }
        }
    }
    thread.start();
}

3、对于一些监听的操作,需要在destory中全部进行释放

 

参考文章:

http://www.jianshu.com/p/c5ac51d804fa

https://juejin.im/entry/5747d70fc26a38006c5744d5

推荐阅读