1.内存优化(一)内存泄漏

目录

  1. 内存泄漏
  1. 如何找到项目中存在的内存泄露
  1. 如何在应用里面避免内存泄露java

  2. 内存泄漏常常出现的例子程序员

1.内存泄漏

C/C++ 本身去分配内存和释放内存——手动管理malloc和free 算法

1.1.什么是内存泄露

内存不在GC掌控以内了。编程

当一个对象已经不须要再使用了,本该被回收时,而有另一个正在使用的对象持有它的引用从而就致使对象不能被回收。这种致使了本该被回收的对象不能被回收而停留在堆内存中,就产生了内存泄漏缓存

了解java的GC内存回收机制:某对象再也不有任何的引用的时候才会进行回收。性能优化

ArrayList<String> list = new Arraylist<String>();
复制代码

(回到顶部) bash

1.2.了解内存分配的几种策略

1.静态的

静态的存储区:内存在程序编译的时候就已经分配好,这块的内存在程序整个运行期间都一直存在。 它主要存放静态数据、全局的static数据和一些常量。app

2.栈式的

在执行函数(方法)时,函数一些内部变量的存储均可以放在栈上面建立,函数执行结束的时候这些存储单元就会自动被释放掉。 栈内存包括分配的运算速度很快,由于内置在处理器的里面的。固然容量有限。ide

3.堆式的

也叫作动态内存分配。有时候能够用malloc或者new来申请分配一个内存。在C/C++可能须要本身负责释放(java里面直接依赖GC机制)。函数

在C/C++这里是能够本身掌控内存的,须要有很高的素养来解决内存的问题。java在这一块貌似程序员没有很好的方法本身去解决垃圾内存,须要的是编程的时候就要注意本身良好的编程习惯。

区别:

1.空间和大小: 堆是不连续的内存区域,堆空间比较灵活也特别大。 栈式一块连续的内存区域,大小是有操做系统觉决定的。

2.效率: 堆管理很麻烦,频繁地new/remove会形成大量的内存碎片,这样就会慢慢致使效率低下。 对于栈的话,他先进后出,进出彻底不会产生碎片,运行效率高且稳定。

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

1.成员变量所有存储在堆中(包括基本数据类型,引用及引用的对象实体)---由于他们属于类,类对象最终仍是要被new出来的。

2.局部变量的基本数据类型和引用存储于栈当中,引用的对象实体存储在堆中。-----由于他们属于方法当中的变量,生命周期会随着方法一块儿结束。

咱们所讨论内存泄露,主要讨论堆内存,他存放的就是引用指向的对象实体。

(回到顶部)

1.3.防止内存溢出

有时候确实会有一种状况:当须要的时候能够访问,当不须要的时候能够被回收也能够被暂时保存以备重复使用。好比:ListView或者GridView、RecyclerView.

加载大量数据或者图片的时候,图片很是占用内存,必定要管理好内存,否则很容易内存溢出。滑出去的图片就回收,节省内存。看ListView的源码——回收对象,还会重用ConvertView。若是用户反复滑动或者下面还有一样的图片,就会形成屡次重复IO(很耗时),那么须要缓存---平衡好内存大小和IO,算法和一些特殊的java类。

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

特殊的java类:利于回收,StrongReference,SoftReference,WeakReference,PhatomReference

类型 回收时机 使用 生命周期
StrongReference 强引用 从不回收 对象的通常保存 JVM中止的时候才会终止
SoftReference 软引用 当内存不足的时候 SoftReference结合
ReferenceQueue构造有效期短
内存不足时终止
WeakReference 弱引用 在垃圾回收的时候 同软引用 GC后终止
PhatomReference 虚引用 在垃圾回收的时候 合ReferenceQueue来跟踪对象被
垃圾回收期回收的活动
GC后终止

开发时,为了防止内存溢出,处理一些比较占用内存大而且生命周期长的对象的时候,能够尽可能使用软引用和弱引用。 软引用比LRU算法更加任性,回收量是比较大的,你没法控制回收哪些对象。

好比使用场景:默认头像、默认图标。 ListView或者GridView、RecyclerView要使用内存缓存+外部缓存(SD卡)

(回到顶部)

1.4.内存泄露例子

单例模式致使内存对象没法释放而致使内存泄露

MainActivity在内存当中泄露了。

public class MainActivity extends AppCompatActivity {

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

        CommUtil commUtil = CommUtil.getInstance(this);

    }
}


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

    public static CommUtil getInstance(Context context){
        if(instance == null){
            instance = new CommUtil(context);
        }
        return instance;
    }

}
复制代码

这个故事告诉咱们能用Application的context就用Application的CommonUtil生命周期跟MainActivity不一致,而是跟Application进程同生同死。

旋转3次:会在内存里面开辟三个MainActivity 实际上3次以上都只会有2个MainActivity。当GC回收的时候会将除了第0个和最后这一个留着其余的都会被回收。

优化两个状况: 1.主动;平时 2.被动,很卡的时候 出现问题的时候。

若是咱们不知道代码内存泄露的状况,如何判断咱们的项目里面有哪些是有内存泄露状况的?

1.凭借工具结合本身的经验来判断。 每每咱们的app在某个时候或者某个操做之后会出现很卡的现象。

1)判断就是查看内存抖动状况

Android Monitor MAT (对Eclipse插件使用的,也有独立分析工具)

查找引用了该对象的外部对象有哪些, 而后一个一个去猜,查找可能内存泄露的嫌疑犯,依据:看(读代码和猜)他们的生命周期是否一致(能够经过快照对比),若是生命周期一致了确定不是元凶。

排除一些容易被回收的(软引用、虚引用、弱引用)

设置监听很容易出现内存泄露

handler.post(callback)
onDestroy(){
handler.removeCallback();
}
复制代码

(回到顶部)

2.如何找到项目中存在的内存泄露

每每作项目的时候状况很是复杂,或者项目作得差很少了想起来要性能优化检查下内存泄露。

如何找到项目中存在的内存泄露的这些地方呢?

2.1.肯定是否存在内存泄露

1.Android Monitors的内存分析

最直观的看内存增加状况,知道该动做是否发生内存泄露。

动做发生以前:GC完后内存1.4M; 动做发生以后:GC完后内存1.6M

2.使用MAT内存分析工具

MAT分析heap的总内存占用大小来初步判断是否存在泄露。 Heap视图中有一个Type叫作data object,即数据对象,也就是咱们的程序中大量存在的类类型的对象。在data object一行中有一列是“Total Size”,其值就是当前进程中全部Java数据对象的内存总量,通常状况下,这个值的大小决定了是否会有内存泄漏。

咱们反复执行某一个操做并同时执行GC排除能够回收掉的内存,注意观察data object的Total Size值,正常状况下Total Size值都会稳定在一个有限的范围内,也就是说因为程序中的的代码良好,没有形成对象不被垃圾回收的状况。

反之若是代码中存在没有释放对象引用的状况,随着操做次数的增多Total Size的值会愈来愈大。 那么这里就已经初步判断这个操做致使了内存泄露的状况。

(回到顶部)

2.2.先找怀疑对象(哪些对象属于泄露的)

MAT对比操做先后的hprof来定位内存泄露是泄露了什么数据对象。(这样作能够排除一些对象,不用后面去查看全部被引用的对象是不是嫌疑)

快速定位到操做先后所持有的对象哪些是增长了(GC后仍是比以前多出来的对象就多是泄露对象嫌疑犯)

技巧:Histogram中还能够对对象进行Group,好比选择Group By Package更方便查看本身Package中的对象信息。

(回到顶部)

2.3. MAT分析hprof来定位内存泄露的缘由所在。(哪一个对象持有了上面怀疑出来的发生泄露的对象)

  • 1.Dump出内存泄露“当时”的内存镜像hprof,分析怀疑泄露的类;
  • 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路径是否正常,此时就要进入代码分析此时这个对象的引用持有是否合理,这就要考经验和体力了!

(好比上面的例子中:旋转屏幕后MainActivity有两个,确定MainActivity发生泄露了,那谁致使他泄露的呢?原来是咱们的CommonUtils类持有了旋转以前的那个MainActivity,那是否合理?结合逻辑判断固然不合理,由此找到内存泄露根源是CommonUtils类持有了该MainActivity实例形成的。怎么解决?罪魁祸首找到了,怎么解决应该不难了,不一样状况解决办法不同,要靠你的智慧了。)

context.getapplictioncontext()能够吗? 能够!!只要让CommonUtils类不直接只有MainActivity的实例就能够了。

通常我是最笨的方法解决
new出来对象,用完后把它 = null;这样算不算优化
假如:
    方法里面定义的对象,要去管吗?通常不须要管。
    本身=null,要本身去控制全部对象的生命周期 判断各类空指针,有点麻烦。
    可是在不少时候去想到主动将对象置为null是很好的习惯。
复制代码

(回到顶部)

3.如何在应用里面避免内存泄露

判断一个应用里面内存泄露避省得很好,怎么看?
当app退出的时候,这个进程里面全部的对象应该就都被回收了,尤为是很容易被泄露的(View,Activity)是否还内存当中。
可让app退出之后,查看系统该进程里面的全部的View、Activity对象是否为0.

工具:

使用AndroidStudio--AndroidMonitor--System Information--Memory Usage查看Objects里面的views和Activity的数量是否为0.
命令行模式:

(回到顶部)

4.内存泄露常常出现的例子

内存泄露(Memory Leak):

进程中某些对象已经没有使用价值了,可是他们却还能够直接或者间接地被引用到GC Root致使没法回收。
当内存泄露过多的时候,再加上应用自己占用的内存,日积月累最终就会致使内存溢出OOM.

内存溢出(OOM):

当应用占用的heap资源超过了Dalvik虚拟机分配的内存就会内存溢出。好比:加载大图片。

4.1.静态变量引发的内存泄露

当调用getInstance时,若是传入的context是Activity的context。只要这个单利没有被释放,那么这个 Activity也不会被释放一直到进程退出才会释放。

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;
	    }
复制代码

(回到顶部)

4.2.非静态内部类引发内存泄露(包括匿名内部类)

错误的示范:

public 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();
}
复制代码

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

当使用软引用或者弱引用的时候,MainActivity难道很容易或者能够被GC回收吗?
GC回收的机制是什么?
当MainActivity不被任何的对象引用。 虽然Handler里面用的是软引用/弱引用,可是并不意味着不存在其余的对象引用该MainActivity。
我连MainActivity都被回收了,那他里面的Handler还玩个屁。

(回到顶部)

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

例子1:

// tv.setOnClickListener();//监听执行完回收对象
//add监听,放到集合里面
tv.getViewTreeObserver().addOnWindowFocusChangeListener(new ViewTreeObserver.OnWindowFocusChangeListener() {
	@Override
	public void onWindowFocusChanged(boolean b) {
		//监听view的加载,view加载出来的时候,计算他的宽高等。

		//计算完后,必定要移除这个监听
		tv.getViewTreeObserver().removeOnWindowFocusChangeListener(this);
	}
});
复制代码

例子2:

SensorManager sensorManager = getSystemService(SENSOR_SERVICE);
Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL);
sensorManager.registerListener(this,sensor,SensorManager.SENSOR_DELAY_FASTEST);
//不须要用的时候记得移除监听
sensorManager.unregisterListener(listener);
复制代码

(回到顶部)

4.4.资源未关闭引发的内存泄露状况

好比:BroadCastReceiver、Cursor、Bitmap、IO流、自定义属性attribute attr.recycle()回收。
当不须要使用的时候,要记得及时释放资源。不然就会内存泄露。

4.5.无限循环动画

没有在onDestroy中中止动画,不然Activity就会变成泄露对象。 好比:轮播图效果。

(回到顶部)

相关文章
相关标签/搜索