您的位置:首页 > 其它

性能优化一之内存与垃圾回收器

2016-12-08 21:07 211 查看
从这篇文章开始,我们开始步入Android性能优化的篇章,由于博主最近正在学习当中,一方面当作自己记录笔记,另一方面分享给大家。

首先我们需要去了解什么是内存回收机制:

               某对象不再有任何的引用的时候才会进行回收。

了解内存分配的几种策略:

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

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

3.堆式的
也叫做动态内存分配。有时候可以用malloc或者new来申请分配一个内存。在C/C++可能需要自己负责释放(java里面直接依赖GC机制)。
在C/C++这里是可以自己掌控内存的,需要有很高的素养来解决内存的问题。java在这一块貌似程序员没有很好的方法自己去解决垃圾内存,需要的是编程的时候就要注意自己良好的编程习惯。

堆与栈的区别:

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

堆管理很麻烦,频繁地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.局部变量的基本数据类型和引用存储于栈当中,引用的对象实体存储在堆中。-----因为他们属于方法当中的变量,生命周期会随着方法一起结束。

这个地方大家需要注意!

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

java中存在着四种引用:

StrongReference 强引用:
回收时机:从不回收 使用:对象的一般保存  生命周期:JVM停止的时候才会终止.

SoftReference 软引用
回收时机:当内存不足的时候;使用:SoftReference<String>结合ReferenceQueue构造有效期短;生命周期:                           内存不足时终止

WeakReference,弱引用
回收时机:在垃圾回收的时候;使用:同软引用; 生命周期:GC后终止

PhatomReference 虚引用
回收时机:在垃圾回收的时候;使用:合ReferenceQueue来跟踪对象呗垃圾回收期回收的活动; 生命周期:GC   后终止

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

但是目前开发中最常用的还是LruCache算法(最近最少使用先回收)

面试问题:

            GC是在什么时候,对什么东西,做了什么事情?

                     java中的GC的发生是系统自动决定的,人为无法预测的。在java中,堆内存被划分成两个不同的区域:

             新生代和老年代。新生代又被划分成三个区域Eden和两个Survivor。目的是为了使 JVM能更好的管理堆内   

             存中的对象,包括内存的分配以及回收。 JVM每次只会使用Eden和其中一块Survivor区域来为对象服务,

            所以无论什么时候,总是有一块Survivor区域是空闲着的。

                    Java中GC分为两种:Minor GC和Full GC。

           Minor GC是发生在新生代中的垃圾收集动作,所采用的是复制算法。当对象在Eden出生后,在经过一次

           Minor  GC后,如果对象还存活,并且能被另外一块survivor区域所容纳,则使用复制算法将这些仍然还

          存活的对象复制到另外一块Survivor 区域。然后清理所使用过的eden以及Survivor区域,并将这些对象的年龄

          设置为1,以后对象在survivor区每经历过一次Minor GC,就将对象的年龄+1,当对象的年龄达到某个值,这

           些对象就会成为老年代。

 

                   Full GC 是发生在老年代的垃圾收集动作,所采用的是标记-清除算法。

           老年代里面的对象几乎个个都是在 Survivor 区域中熬过来的,它们是不会那么容易就 "死掉" 了的。

           因此,Full GC 发生的次数不会有 Minor GC 那么频繁,并且做一次 Full GC 要比进行一次 Minor GC 的时间

           更长。另外,标记-清除算法收集垃圾的时候会产生许多的内存碎片 ( 即不连续的内存空间 ),此后需要为较

           大的对象分配内存空间时,若无法找到足够的连续的内存空间,就会提前触发一次 GC 的收集动作。

上面仅仅是针对Java中的GC来讲,而在Android中GC是怎么样运行的呢?

      垃圾清理的基本概念有:第一,找到未来无法存取的数据,例如所有不受指令操控的内存。第二,回收被利用过的资源。原理简单,但是两百万行编码,跟踪内存分配,在实际操作时却非常困难。如果在程序中有20000个对象分配,垃圾清理会让人困惑,哪一个是没用的?或者,何时启动垃圾清理释放内存?这些问题其实很复杂。好在50年来,我们找到了解决问题的方法,就是Android Runtime中的垃圾清理。比McCarthy最初的方法更高级,速度快且是非侵入性的。经由分配类型,及系统如何有效地组织分配以利GC的运行,并作为新的配置。所有影响android
runtime的内存堆都被分割到空间中,根据这些特点,哪些数据适合放到什么空间,取决于哪个Android版本。



                                                      根据不同类型进行运行时内存的分配

最重要的一点是,每个空间都有预设的大小,在分配目标时要跟踪综合大小,且空间不断地扩大,系统需要执行垃圾清理,以确保内存分配的正常运行,值得一提的是使用不同的AndroidRuntime,GC的运行方式就会不同。例如在Dalvik中很多GC是停止事件,意思是很多指令的运行直到操作完成才会停止。



                                                内存不足时GC处理

当这些GCs所用时间超过一般值,或者一大堆一起执行会耗费庞大的帧象时间,这是很麻烦的事情。



                                          绘图过程中GC回收



                                             GC回收时间过长导致卡顿



                                                 GC回收时间过长导致卡顿

Android工程师花费大量时间降低干扰,确保这些程序以最快的速度运行,话虽如此,在指令中影响程序执行的问题仍然存在,首先程序在任意帧内执行GC所用的时间越多,消除少于16毫秒的呈像障碍,所必需的时间就会变少,如果有许多GC或一大串指令一个接一个地操作,帧象时间很可能会超过16毫秒的呈像障碍,这会导致隐形的碰撞或闪躲。其次,指令流程可能造成GCs强制执行的次数增多,或者,执行时间超过正常值。例如,在一个长期运行的循环最内侧分配囤积对象,很多数据就会污染内存堆,马上就会有许多GC启动,由于这一额外的内存压力,虽然内存环境管理良好,计算比其他语言复杂,内存泄露仍会产生,这些漏洞在GC启动时,通过无法被释放的数据污染内存堆,严重降低可用空间的总量,并以常规方式强制GC的执行。就是这样,如果要减少任意帧内启动GC的次数,需要着重优化程序的内存使用量,从指令的角度看,或许很难追踪这些问题的起因,但是,多亏Android
SDK拥有一组不错的工具。(后面将会继续讲解.....)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: