内存优化

作者: 田间小鹿 | 来源:发表于2017-10-11 18:55 被阅读0次
内存优化主要是分析内存泄露和内存溢出。将从内存是怎么分配,内存怎么出现泄露和溢出,用工具判断什么情况出现泄露,找出泄露点,定位到代码中,然后进行代码优化。还有一些工具介绍,和内存泄露的例子。

1.内存分配

内存分配策略

静态

1.内存在程序编译的时候已经分配好了,这块内存在程序运行期间一直存在。
2.主要放静态数据,全局static数据和一些常量

1.在执行函数或方法时,函数内部变量存储都放在栈中,函数执行结束这些存储单元自动释放
2.栈内存运算速度快,但是数量有限

也叫动态内存分配,是通过对象new出来的对象实例

区别

1.堆是不连续的内存区域,空间比较大
2.栈是一块连续的内存区域,大小由操作系统决定,队列的实现方式,先进后出。

使用

public class Main{
  int a = 1;
  Student s = new Student();
  public void XXX(){
     int b = 1;//栈里面
     Student s2 = new Student();
  }
}

1.成员变量全部存在堆中(包括基本类型,引用及引用的对象实体)----类的对象最终是被new出来的
2.局部变量数据类型和引用存储在栈中,引用的实体对象在堆中-----他们属于方法中的变量。
内存分配

2.内存泄露和内存溢出

内存泄露

1.内存不在gc的掌控中
2.当一个对象已经不须需要使用时,本该被回收,而另外一个正在使用的对象持有它的引用从而导致对象不能被回收。
3.这就导致了本该被回收的对象不能被回收停留里在堆中,产生泄露.

内存溢出

1.进程中某些对象没有使用价值,但是他们却还直接或间接被引用到,GC root无法回收,内存泄露过多时,在加上应用本身占用的内存,日积月累就会导致oom.
2.应用占用资源超过Dalvik虚拟机分配的内存就出现内存溢出.

gc的回收机制

某对象不再有任何的引用的时候才会进行回收。
回收策略
1.标记-清除算法(缺点会产生碎片)
清除算法
2.复制算法
复制算法
复制收集器将内存分为两块一样大小空间,某一个时刻,只有一个空间处于活跃的状态,当活跃的空间满的时候,GC就会将活跃的对象复制到未使用的空间中去,原来不活跃的空间就变为了活跃的空间。
优点:
1 只扫描可以到达的对象,不需要扫描所有的对象,从而减少了应用暂停的时间
缺点:
1.需要额外的空间消耗,某一个时刻,总是有一块内存处于未使用状态
2.复制对象需要一定的开销

3.标记-整理算法

整理算法
标记整理收集器汲取了标记清除和复制收集器的优点,它分两个阶段执行,在第一个阶段,首先扫描所有活跃的对象,并标记所有活跃的对象,第二个阶段首先清除未标记的对象,然后将活跃的的对象复制到堆得底部
该算法极大的减少了内存碎片,并且不需要像复制算法一样需要两倍的空间。
标记-整理算法采用标记-清除算法一样的方式进行对象的标记,但在清除时不同,在回收不存活的对象占用的空间后,会将所有的存活对象往左端空闲空间移动,并更新对应的指针。标记-整理算法是在标记-清除算法的基础上,又进行了对象的移动,因此成本更高,但是却解决了内存碎片的问题。

算法:lrucache(最近最少使用先回收)

java中的种引用类型

引用类型

强引用设置成null就会弱化。arrayList中的使用的就是个例子

private transient Object[] elementData;
public void clear() {
        modCount++;
        // Let gc do its work
        for (int i = 0; i < size; i++)
            elementData[i] = null;
        size = 0;
}

3.如何判断有内存泄露?及定位泄露点

1.确定是否存在内存泄露
1.Android Monitors(android profiler)的内存分析
A.Activity和view数量
  1.(使用的是android studio 3.0以前版本,3.0后没有找着这个按钮)
  当app退出的时候,这个进程里面所有的对象应该就都被回收了,尤其是很容易被泄露的(View,Activity)是否还内存当中。可以让app退出以后,查看系统该进程里面的所有的View、Activity对象是否为0.
  Android Studio--Android Monitor--System Information Memory--Usage查看Objects里面的views和Activity的数量是否为0.
  2.也可以使用命令行进行。
  adb shell & dumpsys meminfo [包名]
adb shell dumpsys meiminfo
B.最直观的看内存增长情况,知道该动作是否发生内存泄露。
内存泄露
2.先找怀疑对象(哪些对象属于泄露的)
1.MAT对比操作前后的hprof来定位内存泄露是泄露了什么数据对象。(这样做可以排除一些对象,不用后面去查看所有被引用的对象是否是嫌疑)
2.快速定位到操作前后所持有的对象哪些是增加了(GC后还是比之前多出来的对象就可能是泄露对象嫌疑犯)
3.技巧:Histogram中还可以对对象进行Group,比如选择Group By Package更方便查看自己Package中的对象信息。
3.MAT分析hprof来定位内存泄露的原因所在。(哪个对象持有了上面怀疑出来的发生泄露的对象)
1)Dump出内存泄露“当时”的内存镜像hprof,分析怀疑泄露的类;

2)把上面2得出的这些嫌疑犯一个一个排查个遍。

(1)进入Histogram,过滤出某一个嫌疑对象类
(2)然后分析持有此类对象引用的外部对象(在该类上面点击右键List Objects--->with incoming references)
(3)再过滤掉一些弱引用、软引用、虚引用,因为它们迟早可以被GC干掉不属于内存泄露 (在类上面点击右键Merge Shortest Paths to GC Roots--->exclude all phantom/weak/soft etc.references)
(4)逐个分析每个对象的GC路径是否正常此时就要进入代码分析此时这个对象的引用持有是否合理,这就要考经验和体力了!

到此就得到了对应的错误函数 CommUtil

4.查找代码,优化代码

上面是查询内存泄露的一个流程,可以按照自己的代码去确定泄露点,然后进行优化。

5.使用快捷的定位方式(leakcanary)

Square公司 可以直接在手机端查看内存泄露的工具。

//LeakCanary原理总结
//1. 在Activity的onDestory()中,将该Activity添加到内存泄漏监控队列中
//2. 在后台线程检查引用是否被清除,如果没有,调用GC
//3. 如果引用还是未被清除,把heap内存dump到一个 .hprof 文件中
//4. 使用一个独立进程中的服务计算出到GC Roots的最短强引用路径来判断是否发生Leak
//5. 建立导致泄漏的引用链,结果在Log中打印出来

Application
  install()
LeakCanary
  androidWatcher()
RefWatcher
  new AndroidWatcherExecutor() --->dumpHeap()/analyze()(--->runAnalysis())--->Hprof文件分析
  new AndroidHeapDumper()
  new ServiceHeapDumpListener
//使用
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
 
Application
LeakCanary.install(this);

6.内存抖动

在极短的时间内为对象分配内存。在Allocation Tracker中显示连续的内存分配。
减少内存抖动的方法:
1.使用计算方法优化算法
2.使用合适的数据结构
StringBuffer 与 String
 
String是对象不是常量,在字符进行拼接时候,会新产生一个String
1.
String str1 = "1232";
String str2 = "22222";
String str3 = str1 + str2;
StringBuffer str = new StringBuidler.append(Str1).append(str2);  //性能优
 
2 
//性能差的实现
StringBuffer str = new StringBuilder().append("Name:").append("GJRS");
//性能好的实现
String Str = "Name:" + "GJRS";

7.内存泄露经常出现的例子

1.静态变量引起。当调用单例时,如果传入的context是Activity的context。尽量使用getAppationContext。


public class CommUtil {
    private static CommUtil instance;
    private Context context;
    private CommUtil(Context context){
        this.context = context;
    }

    public static CommUtil getInstance(Context mContext){
        if(instance == null){
            instance = new CommUtil(mContext);
        }
//        else{
//            instance.setContext(mcontext);
//        }
        return instance;
    }

    public void setContext(Context context) {
        this.context = context;
    }

}
 
调用
 CommUtil commUtil = CommUtil.getInstance(this);
//解决办法:使用Application的上下文
//CommonUtil生命周期跟MainActivity不一致,而是跟Application进程同生同死。
//CommUtil commUtil = CommUtil.getInstance(getApplicationContext());
 

2.非静态内部类引起的内存泄露(匿名内部类)

错误的示范:


Paste_Image.png

(1)
解决方案:
将非静态内部类修改为静态内部类。
(静态内部类不会隐式持有外部类)

    //加上static,里面的匿名内部类就变成了静态匿名内部类
    public static void loadData(){//隐式持有MainActivity实例。MainActivity.this.a
        new Thread(new Runnable() {
            @Override
            public void run() {
                while(true){
                    try {
                        //int b=a;
                       Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

(2)

new Timer().schedule(new TimerTask() {
    @Override
    public void run() {
        while(true){
            try {
                //int b=a;
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}, 20000);//这个线程延迟5分钟执行
 
//意外重启也不会调用
 activity onDestroy把timer.cancel掉然后赋空

(3)
错误;

//mHandler是匿名内部类的实例,会引用外部对象MainActivity.this。如果Handler在Activity退出的时候,它可能还活着,这时候就会一直持有Activity。
private Handler mHandler = new Handler(){
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        switch (msg.what){
            case 0:
                //加载数据
                break;

        }
    }
};
 
mHandler.sendEmptyMessage(0);
mHandler.sendMessageAtTime(msg,10000);//atTime

解决方法:

//mHandler是匿名内部类的实例,会引用外部对象MainActivity.this。如果Handler在Activity退出的时候,它可能还活着,这时候就会一直持有Activity。
private static class  MyHandler extends Handler
{
    //private MainActivity mActivity;//直接持有了一个外部类的强引用,会内存泄露
    private WeakReference<MainActivity> mActivity;//设置软引用保存,当内存一发生GC的时候就会回收。
    public MyHandler(MainActivity activity) {
        this.mActivity = new WeakReference<MainActivity>(activity);
    }

    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        MainActivity main =  mActivity.get();
        
        if(main==null||main.isFinishing()){
            return;
        }
        
        switch (msg.what){
            case 0:
                //加载数据 MainActivity.this.a;
                // 有时候确实会有这样的需求:需要引用外部类的资源。怎么办?
                Button b = main.cacheButton;
                break;
        }
    }

}

当使用软引用或者弱引用的时候,MainActivity难道很容易或者可以被GC回收吗?GC回收的机制是什么?
当MainActivity不被任何的对象引用。虽然Handler里面用的是软引用/弱引用,但是并不意味着不存在其他的对象引用该MainActivity。

原因:声明周期不同步

3.Adapter中引用了Activity如何避免内存泄漏

adapter不能持有activity的引用,否则可能会因为adapter里面可能会做一些耗时操作,当activity finish时会因为被adapter持有引用而导致activity无法被回收,从而导致内存泄漏。
解决方法:
(1)不持有Activity对象的情况下怎么和Activity交互
1.1 首先处理getView()中的inflate()
当我们inflate一个xml时,完全可以使用parent的context,实现如下:
Paste_Image.png

1.2 点击事件,可以用回调接口
使用回调接口的方式来实现不持有activity的情况下,与Activity愉快的交互,实现如下:

Paste_Image.png Paste_Image.png

2.adapter写在Activity里面的话只需要加个static关键字(变为静态内部类)就行了,其他和写在外面是一样的。

Paste_Image.png

4.不需要用的监听未移除会发生内存泄露

  1. add和remove配对使用 放入一个数组中
cacheButton.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
    @Override
    public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
        //处理

        //移除
        cacheButton.removeOnLayoutChangeListener(this);
    }
});

2.set 使用以后会自动释放

3.register 和 unregister

SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL);
SensorEventListener listener = new SensorEventListener() {
    @Override
    public void onSensorChanged(SensorEvent event) {
        
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {

    }
};
sensorManager.registerListener(listener,sensor,SensorManager.SENSOR_DELAY_FASTEST);
sensorManager.unregisterListener(listener);

5.资源未关闭引起的内存泄露情况

BroadCastReceiver
Cursor
Bitmap
Io流
自定义的attribute   attr.recycle()回收

当不需要使用的时候,要及时释放资源(消耗)

6.无限循环的动画

在不使用的时候在OnDestroy中停止

7. 使用IntentService

如果Service停止失败也会导致内存泄漏。
因为系统会倾向于把这个Service所依赖的进程进行保留,如果这个进程很耗内存,就会造成内存泄漏。

解决方案:
所以推荐使用IntentService,它会在后台任务执行结束后自动停止,从而避免了Service停止失败导致发生内存泄漏的可能性。

Paste_Image.png
继承service的抽象类
  abstract class IntentService extends Service
 
  在onCreate里面起了一个线程
  HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
    thread.start();
 
  然后用thread的looper起了一个Handler
  mServiceLooper = thread.getLooper(); 
   mServiceHandler = new ServiceHandler(mServiceLooper);
 
  在onStart里面,将启动的Intent交给ServiceHandler处理
  @Override public void onStart(Intent intent, int startId) { 
         Message msg = mServiceHandler.obtainMessage(); 
         msg.arg1 = startId; 
         msg.obj = intent;
         mServiceHandler.sendMessage(msg); 
    }
 
  在ServiceHandler的handleMessage里面交给抽象方法onHandleIntent处理start的消息
  @Override public void handleMessage(Message msg) {
        onHandleIntent((Intent)msg.obj);
        stopSelf(msg.arg1); 
    }
    protected abstract void onHandleIntent(Intent intent);
  
   在onHandleIntent完了stopSelf

==内存泄露不是在项目完成后才进行的,而是在写代码的时候就开始的。==

相关文章

网友评论

    本文标题:内存优化

    本文链接:https://www.haomeiwen.com/subject/vzgwyxtx.html