有关性能优化的一点见解

App 分配内存大小:16M、32M、64M三个等级。

一、内存泄漏的定义

什么是内存泄漏:内存不在掌握之内了,换句话说:当一个对象已经不需要使用了,本该被回收时,而又另外一个正在使用的对象持有它的引用从而导致该对象不能被回收,这种导致了本该被回收的对象不能被回收而停留在堆内存中,就产生了内存泄漏。
发生内存泄漏主要是Java和Android模块代码,C/C++需要手动去分配内存和释放内存(malloc/free)。
Java的GC内存回收机制:某对象不再任何的引用的时候才会进行回收。

二、了解内存分配的几种策略

1. 静态的

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

2. 栈式的

在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
它存放函数的参数值,局部变量等值。其操作方式类似于数据结构中的栈。

3.堆式的

亦称动态内存分配。程序在运行的时候用malloc或new申请任意大小的内存,程序员自己负责在适当的时候用free或delete释放内存。动态内存的生存期可以由我们决定,如果我们不释放内存,程序将在最后才释放掉动态内存。 但是,良好的编程习惯是:如果某动态内存不再使用,需要将其释放掉,否则,我们认为发生了内存泄漏现象。在C/C++可能需要自己负责释放,Java里面直接使用GC机制释放内存。

例如:

public class main{
	int a = 1;
	Student s = new Student();
	public void ***() {
		int b = 1;
		Student s2 = new Student();
	}
}

其中: int a = 1; Student s = new Student(); 这两行中的 "a"和"s"这两个变量是存放在堆里面的

int b = 1; 这行中的变量 “b”是存放在栈
Student s2 = new Student(); 这条语句中的“Student s2”为一个对象的引用,"s2"它是存放在栈中,“=”表示将对象引用指向对象, “new Student()”表示创建对象,它是存储在堆中

说明:
1)成员变量全部存储在堆中(包括基本数据类型,引用及引用的对象实体),因为它们属于类,类对象最终还是要被new出来的。
2)局部变量的基本数据类型和引用存储于栈中,引用的对象实体存储在堆中,因为它们属于方法当中的变量,生命周期会随着方法一起结束。
我们所讨论的内存泄漏,主要是讨论堆内存,它存放的就是引用指向的对象实体。

有时候会有一种情况:当需要的时候可以访问,当不需要的时候可以被回收也可以暂时保存以备重复使用。
比如:ListView或者GridView、RecyclerView加载大量数据或者图片的时候,图片非常占用内存,一定要管理好内存,不然很容易出现内存溢出,滑出去的图片就回收,节省内存,看ListView的源码,回收对象还会重用ConvertView,如果用户反复滑动或者下面还有同样的图片,就会造成多次重复IO(很耗时),那么需要缓存—平衡好内存大小和IO,算犯法和一些特殊的java类。
算法:Lrucache(最近最少未使用先回收)
特殊的java类:利于回收,StrongReference(强引用),SoftReference(软引用)、WeakReference(软引用)、PhatomReference(虚引用)。

四种引用的区别:

StrongReferenceSoftReferenceWeakReferencePhatomReference
回收时机从不回收当内存不足时在垃圾回收的时候在垃圾回收的时候
使用对象的一般保存SoftReference结合ReferenceQueue使用,构造有效期短WeakReference结合ReferenceQueue使用,构造有效期短结合ReferenceQueue使用,来跟踪对象有没有被垃圾回收器回收的活动
生命周期JVM停止的时候才会终止内存不足时终止GC后终止GC后终止

开发中,为了防止内存溢出,处理一些比较占用内存大并且生命周期长的对象的时候,可以尽量使用软引用和弱引用。

软引用比LRU算法更加任性,回收量是比较大的,你无法控制回收哪些对象,比如使用场景、默认头像、默认图标。
ListIView或者GridView、RecyclerView要使用内存缓存 + 外部缓存(SD卡)。

也有其他的说法:将内存分为:
动态存储区、静态存储区、堆和栈的区别.
在这里插入图片描述
内存中用户存储空间的分配情况(三种):
① 程序区:存放程序语句
② 静态存储区
③ 动态存储区

由C/C++编译的程序占用的内存分为以下几个部分:
① 栈区(stack)
② 堆区(heap)
③ 全局区(静态区)(static)
④ 文字常量区-----常量字符串就是放在这里的。 程序结束后由系统释放
⑤ 程序代码区

堆和栈的区别

(一) 申请方式
  • 栈(satck):由系统自动分配
  1. 程序运行时由编译器自动分配的一块连续的内容,存放函数的参数值,局部变量的值等。 例如,声明在函数中一个局部变量int b;系统自动在栈中为b开辟空间。
  2. 程序结束时由编译器自动释放
  3. 栈由系统自动分配,程序员无法控制
  4. 只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。
  5. 存取方式,先进后出
  • 堆(heap):
  1. 在内存开辟另一块不连续的存储区域。一般由程序员分配释放,
  2. 若程序员不释放,程序结束时由系统回收
  3. 首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。
  4. 需程序员自己申请(调用malloc,realloc,calloc),并指明大小,并由程序员进行释放。容易产生memory leak.
(二) 申请大小的限制
  • :在windows下,栈是向底地址扩展的数据结构,是一块连续的内存区域(它的生长方向与内存的生长方向相反)。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在 WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数)。

    栈的大小是固定的。如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。

  • 堆是高地址扩展的数据结构(它的生长方向与内存的生长方向相同),是不连续的内存区域。这是由于系统使用链表来存储空闲内存地址的,自然是不连续的,而链表的遍历方向是由底地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。

(三) 系统响应:
  • :只要栈的空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出

  • :首先应该知道操作系统有一个记录空闲内存地址的链表,但系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的free语句才能正确的释放本内存空间。另外,找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。

说明:
(1)对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。

(2)对于栈来讲,则不会存在这个问题,它是先进后出,进出完全不会产生碎片,运行效率高且稳定。

(四) 申请效率的比较

(1)栈由系统自动分配,速度快。但程序员是无法控制的

(2)堆是由malloc分配的内存,一般速度比较慢,而且容易产生碎片,不过用起来最方便。

另外,在WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是在栈,是直接在进程的地址空间中保留一块内存,虽然用起来最不方便。但是速度快,也最灵活。

(五) 堆和栈中的存储内容
  • :在函数调用时,第一个进栈的主函数中后的下一条语句的地址,然后是函数的各个参数,参数是从右往左入栈的,然后是函数中的局部变量。注:静态变量是不入栈的。当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续执行。
  • :一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容由程序员安排。
(六) 存取效率的比较

(1)堆:char *s1=”hellow tigerjibo”;hellow tigerjibo是在编译时就确定的

(2)栈:char s1[]=”hellow tigerjibo”;hellow tigerjibo是在运行时赋值的

数组比用指针速度更快一些,指针在底层汇编中需要用edx寄存器中转一下。

数组在栈读取时直接就把字符串中的元素读到寄存器cl中,而堆则要先把指针值读到edx中,再根据edx读取字符,显然慢了。

补充:

  • 栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。
  • 堆则是C/C++函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法(具体的算法可以参考数据结构/操作系统)在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后进行返回。显然,堆的效率比栈要低得多。
(七) 分配方式:

(1)堆都是动态分配的,没有静态分配的堆。

(2)栈有两种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的。它的动态分配是由编译器进行释放,无需手工实现。

三、内存泄漏分析工具

① Android Monitor
② MAT(Memory Analyzer Tools)对于Eclipse插件使用的,也有独立分析工具。
MAT、Memory Monitor属于Android Monitor中一个模块、HeapTool查看堆信息
③ Allaction Tracking
追踪内存分配信息,可以很直观地看到某个操作内存是如何进行一步一步地分配的。
④LeakCanary
它属于Square公司,可以直接在手机端查看内存泄漏的工具。
实现原理:本质上还是用命令控制生成hprof文件分析检查内存泄漏,然后发送通知。
⑤ Lint分析工具
Android studio很方便,很好用。
具体功能:
1)检查资源文件是否有没有用到的资源
2)检查常用内存泄漏
3)SDK版本安全问题
4)是否有的代码没有用到
5)代码的规范,甚至驼峰命名也会检查
6)自动生成的罗列处理
7)没用的导包
8)可用的bug

四、常见的一些内存泄漏问题

1、判断一个应用里面是否存在内存泄漏,怎么看呢?

答:当App退出的时候,这个进程里面所有的对象应该都被回收,尤其是很容易泄漏的View、Activity是否还在内存中。
可以让App退出以后,查看系统该进程里面的所有View、Activity对象是否为0。
查看方法:使用Android Studio --> Android Monitor -->System Information -->Memory Usage 查看Objects里面的Views和Activity数量是否为0。
内存泄漏(Memory Leak):进程中某些对象已经没用使用价值了,但是它却还可以直接或者间接的被引用到,以至导致GC Root 无法回收。
当内存泄漏过多的时候,再加上应用本身占用的内存,日积月累最终就会导致内存溢出OOM。
内存溢出OOM(Out Of Memory):当应用占用的heap资源超过了Dalvik虚拟机分配的内存就会内存溢出,比如:加载大图片。

2、静态变量引起的内存泄漏(单例造成的内存泄漏)

public class CommonUtil {  // 单例模式
	private static CommonUtil instance;
	private Context context;
	private CommonUtil(Context context) {
		this.context = context;
	}
	public static CommonUtil getInstance(Context mContext) {
		if (instance == null) {
			instance = new CommonUtil(mContext);   //这行会发生内存泄漏
		}
		return instance;
	}
}

当调用getInstance时,如果传入的mContext是Activity的context,只要这个单例没有被释放,那么这个Activity也不会被释放一直到进程退出才会释放。
解决办法:
CommonUtil util = CommonUtil.getInstance(getApplicationContext());
传入的参数为getApplicationContext()而不是Activity的this对象。

3、非静态内部类引起内存泄漏(包括匿名内部类/Handler造成的内存泄漏)

可以参考(非静态内部类内存泄露实例.)
错误示范:a 是在MainActivity类中定义的一个变量

class 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();
}

解决办法:将非静态内部类修改为静态内部类(静态内部类不会隐式持有外部类)或者使用弱引用
修改方法:static class loadData{…}
如果匿名内部类的生命周期比Activity的生命周期长,则不能使用非静态内部类。

3、不需要用的监听未移除会发生内存泄漏

例1:

TextView tv  = (TextView) findViewById(R.id.tv);
// tv.setOnClickListener();   // 监听执行完会回收对象
tv.setViewTreeObserver().addOnWindowFocusChangeListener(new ViewTreeObserver.OnWindowFocusChangeListener()) {
	@Override
	public void onWindowFocusChanged(boolean b) {
		// 监听view的加载,view加载出来的时候计算它的宽高等,
		// 计算完后,一定要移除这个监听
		tv.getViewTreeObserver().removeOnWindowFocusChangeListener(this);
	}
}

例2:

sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_FASTEST());
// 不需要用的时候记得移除监听
sensorManager.unregisterListener(listener);

4、资源未关闭引起的内存泄漏情况

比如:Cursor、Bitmap、I/O流、自定义属性attribute(attr.recycle()回收)。
当不需要使用的时候,要记得及时释放资源,否则就会内存泄漏。

5、无限循环动画

没有在onDestory中停止动画,否则Activity就会变成内存泄漏对象,比如:轮播图效果。

6、线程造成的内存泄漏

7、webview造成的内存泄漏

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值