一、Android性能优化之内存泄露
前言
性能优化目的:
1.如何去优化自己的项目,运行更流畅。
现实App进程分配内存空间: 16M 32M 64M
2..以后开发项目的时候就要从一开始把项目做好
内存泄露
什么是内存泄露? 内存不在GC管控之内
当一个对象已经不需要再使用时,本该被回收时,而有另外一个正在使用的对象持有它的引用从而导致对象不能被回收。这种导致了本该被回收的对象不能被回收而停留在堆内存中,就产生了内存泄露。
不是所有指令都执行得又快又好,下面介绍内存及它如何影响系统运行。普遍认为,多数程序语言接近硬件或高性能,如C、C++和Fortran,通常程序员会自己管理内存,高手工程师对内存的分配,会慎重处理,并在未来结束使用时再次分配,一旦确认何时及怎样分配内存,内存管理的品质就依赖于工程师的技能跟效率。实际情况是工程师们,不都会去追踪那零碎的内存碎片。程序开发是个混乱又疯狂的过程,内存通常都没办法完全被释放,这些被囚禁的内存叫内存泄露。
内存泄露内存泄露占用了大量资源,这些资源其实可以更好地使用,为减少泄露引起的混乱、负担、甚至资金损失,便有了内存管理语言。
跟踪内存分配
这些语言在运行时跟踪内存分配,以便当程序不再需要时释放系统内存,完全不用工程师亲自操作,这些内存回收艺术或科学,在内存管理环节下叫垃圾清理。这个设计概念在1959年,当初为了解决lisp语言问题,由John McCarthy发明的。
垃圾清理的基本概念有:
第一,找到未来无法存取的数据,例如所有不受指令操控的内存。
第二,回收被利用过的资源。原理简单,但是两百万行编码,跟4gigs的分配,在实际操作时却非常困难。如果在程序中有20000个对象分配,垃圾清理会让人困惑,哪一个是没用的?或者,何时启动垃圾清理释放内存?这些问题其实很复杂。好在50年来,我们找到了解决问题的方法,就是Android Runtime中的垃圾清理。比McCarthy最初的方法更高级,速度快且是非侵入性的。经由分配类型,及系统如何有效地组织分配以利GC的运行,并作为新的配置。所有影响android runtime的内存堆都被分割到空间中,根据这些特点,哪些数据适合放到什么空间,取决于哪个Android版本。
了解内存分配的几种策略:
1.静态的
在编译时就能确定每个数据目标在运行时刻的存储空间需求,因而在编译时就可以给他们分配固定的内存空间.这种分配策略要求程序代码中不允许有可变代码结构(比如可变数组的存在),也不允许有嵌套或者递归结构的出现,因为它们都会导致编译程序无法计算准确的存储空间需求。
静态的存储区,内存在分配的时候就已经分配好了,这块的内存在程序在整个运行期间内一直存在。
2.栈式的
栈式存储分配也可称为动态存储分配,是由一个类似于堆栈的运行栈来实现的.和静态存储分配相反,在栈式存储方案中,程序对数据区的需求在编译时是完全未知的,只有到运行的时候才能够知道,但是规定在运行中进入一个程序模块时,必须知道该程序模块所需的数据区大小才能够为其分配内存.和我们在数据结构所熟知的栈一样,栈式存储分配按照先进后出的原则进行分配。
在执行函数(方法)时,函数一些内部变量的存储都可以放在栈上创建,函数执行结束的时候这些存储单元就会自动被释放掉。栈内存包括分配的运算速度很快,因为内置在处理器里面的。当然容量有限。
3.堆式的
堆式存储分配则专门负责在编译时或运行时模块入口处都无法确定存储要求的数据结构的内存分配,比如可变长度串和对象实例.堆由大片的可利用块或空闲块组成,堆中的内存可以按照任意顺序分配和释放.
在C/C++可能需要自己负责释放(java里面直接额依赖GC机制)
栈式和堆式区别:
从堆和栈的功能和作用来通俗的比较,堆主要用来存放对象的,栈主要是用来执行程序的.
heap:是由malloc之类函数分配的空间所在地。地址是由低向高增长的。
stack:是自动分配变量,以及函数调用的时候所使用的一些空间。地址是由高向低减少的。
使用栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自由度小。
使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大。
public class Main{
int a = 1;//堆里面
Student s = new Student();//堆里面
public void XXX(){//堆里面
int b = 1;//栈里面
Student s2 = new Student();
}
}
1.成员变量全部存储在堆中(包括基本数据类型,引用和引用的对象实体) --- 因为它们属于类,类最终还是要被new出来。
2.局部变量的基本数据类型和引用存储于栈当中,引用的对象实体存储于在堆中。-----因为他们属于方法当中的变量,生命周期会随着方法一起结束。
我们所讨论的内存泄漏,主要是讨论堆存储,它存放的是引用指向的对象实体。
有时候确实会有一种情况:当需要的时候可以访问,当不需要的时候可以被回收也可以被暂时保存以备重复使用。
比如:ListView或者GridView、REcyclerView加载大量数据或者图片的时候,
图片非常占用内存,一定要管理好内存,不然很容易内存溢出。
滑出去的图片就回收,节省内存。看ListView的源码----回收对象,还会重用ConvertView。
如果用户反复滑动或者下面还有同样的图片,就会造成多次重复IO(很耗时),
那么需要缓存---平衡好内存大小和IO,算法和一些特殊的java类。
算法:lrucache(最近最少使用先回收)
特殊的java类:利于回收,StrongReference,SoftReference,WeakReference,PhatomReference
StrongReference --- 强引用:
StrongReference 是 Java的默认引用实现, 它会尽可能长时间的存活于 JVM 内, 当没有任何对象指向它时 GC 执行后将会被回收
回收时机:从不回收 使用:对象的一般保存 生命周期:JVM停止的时候才会终止
SoftReference --- 软引用
SoftReference 于 WeakReference 的特性基本一致, 最大的区别在于 SoftReference 会尽可能长的保留引用直到 JVM 内存不足时才会被回收(虚拟机保证), 这一特性使得 SoftReference 非常适合缓存应用
回收时机:当内存不足的时候;使用:SoftReference<String>结合ReferenceQueue构造有效期短;生命周期:内存不足时终止
WeakReference --- 弱引用
WeakReference 是一个弱引用, 当所引用的对象在 JVM 内不再有强引用时, GC 后 weak reference 将会被自动回
回收时机:在垃圾回收的时候;使用:同软引用; 生命周期:GC后终止
PhatomReference --- 虚引用
“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果一个对象仅>持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。
虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用>队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对
象的内存之前,把这个虚引用加入到与之 关联的引用队列中。
ReferenceQueue queue = new ReferenceQueue ();
PhantomReference pr = new PhantomReference (object, queue);
程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
回收时机:在垃圾回收的时候;使用:合ReferenceQueue来跟踪对象被垃圾回收期回收的活动; 生命周期:GC后终止
开发时,为了防止内存溢出,处理一些比较占用内存大并且生命周期长的对象的时候,可以尽量使用软引用和弱引用。软引用比LRU算法更加任性,回收量是比较大的,你无法控制回收哪些对象。
比如使用场景:默认头像、默认图标。
ListView或者GridView、RecyclerView要使用内部缓存+外部缓存(SD卡)
-----------------------------内存泄漏例子--------------------------
单例模式导致内存对象无法释放而导致内存泄漏
public class CommonUtils {
private static CommonUtils instance;
private Context context;
private CommonUtils(Context context) {
this.context = context;
}
public static CommonUtils getInstance(Context context) {
if (instance == null) {
instance = new CommonUtils(context);
}
return instance;
}
}
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
CommonUtils commonUtils = CommonUtils.getInstance(this);
}
}
运行这个简单的单例模式程序,进行多次横竖屏切换,发现可用内存越来越小,存在内存泄漏现象,最终导致内存溢出。
内存泄漏分析原因:
点击Monitors->Memory中 Dump Java Heap 采集记录各个类内存使用情况,如下图:
内存泄漏原因根据图我们发现,进行多次横竖屏切换时,生产了9个MainActivity。多个MainActivity所占用的内存资源没有被GC及时回收,导致内存泄漏。
总结
我们能用Application的context就用Application的
CommonUtils 生命周期是跟Application进程同生同死。
特别感谢
动脑学院Ricky