内存(Memory)是计算机的重要部件,也称主存储器,它用于暂时存放CPU中的运算数据,以及与硬盘等外部存储器交换的数据。Android中,内存是如何分配的?当启动一个android程序时,会启动一个dalvik vm进程,系统会给它分配固定的虚拟内存空间(16M,32M不定),这块内存空间会映射到ram上某个区域。然后这个android程序就会运行在这块空间上。安卓采用弹性内存分配机制。也就是说一开始并不会分配太多的内存。而是给每一个APP的进程分配一个小额的量。这个小额的量跟手机的内存大小有关。当进程内存不够的时候,安卓会再分配一些内存给各个进程,它是有限度的,如果超过这个限度,则会出现OOM异常。
Android中应用是跑在 Android虚拟机中,它对jvm进行了兼容,Java运行时的内存模型
方法区:与Java一样,是各个线程所共享的,用于存储已被虚拟机加载类信息、常量、静态变量、即时编译器编译后的代码等数据。
Java堆:是虚拟机管理内存中最大的一块,被所有线程共享,该区域用于存放对象实例,Java堆是内存回收的主要区域。
Java虚拟机栈:线程私有的,其生命周期和线程一致,每个方法执行时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
本地方法栈:存放native方法的相关信息
程序计数器:一块较小内存区域,指向当前所执行的字节码。
我们开发的应用时,一般程序里所占内存区域是堆区,这一块区域也是经常导致内存紧张的区域,所以有专门对这一块设计了垃圾回收机制。常见垃圾回收算法如下:
1 引用计数法 记录每个引用的被引用个数,当引用个数为0时代表成为垃圾,应该被清理。
优点: 实现简单 引用减少至0时,实时回收内存,内存利用率高 回收操作可并发运行,无需暂停应用线程
缺点: 1 无法解决环状引用,如a引用b,b引用a。两个都是垃圾却不会被清理 2 需要维护计数器
Java四种引用
强引用
是指创建一个对象并把这个对象赋给一个引用变量。
强引用有引用变量指向时永远不会被垃圾回收,JVM宁愿抛出OutOfMemory错误也不会回收这种对象。
软引用(SoftReference)
一个对象具有软引用,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。
弱引用(WeakReference)
弱引用也是用来描述非必需对象的,当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象
虚引用(PhantomReference)
虚引用和前面的软引用、弱引用不同,它并不影响对象的生命周期,在任何时候都可能被垃圾回收器回收。
Java中提供这四种引用类型主要有两个目的:
第一是可以让程序员通过代码的方式决定某些对象的生命周期;
第二是有利于JVM进行垃圾回收。
2 可达性分析法(根搜索算法) 确定根对象(GC ROOT),顺着根对象遍历,凡是被根对象直接或者间接引用的都不是垃圾,剩下就是垃圾。
优点: 1. 不存在循环引用问题 2. 不存在引用计数法的空间开销
缺点: 3. 内存利用率低(在GC回收内存前,垃圾对象占用的内存无法复用)
哪些对象可以作为Root对象?
1 方法区中静态属性引用的对象 2 方法区中常量引用的对象 3 本地方法栈中引用的对象(Native对象) 特征:它们的生命周期较长。
3 CMS(Concurrent Low Pause Collector) 一个老年代收集器,是JDK1.4后期开始引用的新GC收集器,CMS的一大特点,就是用两次短暂的暂停来代替串行整理算法时候的长暂停。使用算法:标记 - 清理.
初始标记 : 标记与 GC Roots 有引用链的对象 ; 该操作速度快 , 该步骤需要暂停用户线程 ;
并发标记 : GC Roots 追踪 , 从初始标记结果集合中标记出存活对象 , 不能保证所有的存活对象都被标记 ; 该步骤与应用程序并发执行 ;
重新标记 : 上一步并发标记 GC 线程与用户程序并发期间的标记有部分变化 , 修正这部分标记信息 , 之后暂停用户线程 , 开始标记 ; 该暂停操作要比初始标记步骤暂停时间长 ;
并发清除 : 回收所有 GC Roots 不可达对象 ;
CMS优点: 1 降低GC停顿时间,关注应用响应速度;
CMS缺点: 1、内存碎片。由于使用了 标记-清理 算法,该算法不会对内存碎片进行清理,导致内存空间中会产生内存碎片。 2、需要更多的CPU资源。由于使用了并发处理,很多情况下都是GC线程和应用线程并发执行的,这样就需要占用更多的CPU资源。
4 CC(Concurrent Copying) Android 8开始默认垃圾收集器,CC 通过在不暂停应用线程的情况下并发复制对象来执行堆碎片整理。这是在读取屏障的帮助下实现的,读取屏障会拦截来自堆的引用读取,无需应用开发者进行任何干预。GC 只有一次很短的暂停,对于堆大小而言,该次暂停在时间上是一个常量。
优点:降低GC停顿时间
Android内存回收机制
默认进程回收机制: 当系统内存不足时,系统将激活内存回收过程。为了不因内存回收影响用户体验(如杀死当前的活动进程), 回收优先级: Android 基于进程中运行的组件及其状态规定了默认的五个回收优先级:
IMPORTANCE_FOREGROUND:前台进程,目前正在屏幕上显示的进程和一些系统进程。 IMPORTANCE_VISIBLE:可见进程,可见进程是一些不再前台,但用户依然可见的进程,比如输入法、天气、时钟等。
IMPORTANCE_SERVICE:服务进程,拨号、邮件存储之类的。 IMPORTANCE_BACKGROUND:后台进程,启动后被切换到后台的进程。
IMPORTANCE_EMPTY:没有任何东西在内运行的进程,有些程序,比如BTE,在程序退出后,依然会在进程中驻留一个空进程,这个进程里没有任何数据在运行,作用往往是提高该程序下次的启动速度或者记录程序的一些历史信息。
Low Memory Killer机制
在系统内存减少到达一个阈值时,系统就会开始根据进程的优先级来进行回收机制杀掉一部分进程来释放出内存供后面需要启动的App使用。这套回收内存的机制就叫Low Memory Killer。Linux内核分配给每个系统进程的一个值,叫做oom_adj值。此值越大,进程的优先级越低,越容易被回收,普通应用进程的oom_adj值是>=0,而系统进程的oom_adj值是<0的。
当内存不足,触发内存阈值时,出现两个普通应用存在相同的adj值时,系统应该先回收哪一个应用进程? 答:占用内存较大的那个
内存泄露
指程序在申请内存后,无法释放已申请的内存空间,导致系统无法及时回收内存并且分配给其他进程使用。通常少次数的内存无法及时回收并不会到程序造成什么影响,但是如果在内存本身就比较少获取多次导致内存无法正常回收时,就会导致内存不够用,最终导致内存溢出。
内存溢出(OOM)
指程序申请内存时,没有足够的内存供申请者使用,导致数据无法正常存储到内存中。
内存抖动
主要是由于频繁的创建和销毁对象导致的,这种循环往复的状态就叫做内存抖动Memory Churn;
内存泄露与内存溢出(OOM)的关系以及区别
关系:内存泄露最终会导致内存溢出,由于系统中的内存是有限的,如果过度占用资源而不及时释放,最后会导致内存不足,从而无法给所需要存储的数据提供足够的内存,从而导致内存溢出。 区别:内存泄露是由于GC无法及时或者无法识别可以回收的数据进行及时的回收,导致内存的浪费;内存溢出是由于数据所需要的内存无法得到满足,导致数据无法正常存储到内存中。内存泄露的多次表现就是会导致内存溢出。
内存溢出与内存抖动的关系
如果说分配的频率大于回收的频率,那么最终就会导致OOM的发生。
Android中如何进行内存优化 ?
1.避免在Application和Activity的onCreate方法中做过多工作,这会占用过多内存。可以将不必要的初始化工作放到onStart或延迟加载。
2.优化布局,减少视图层级,可以通过merge标签减少不必要的ViewGroup,过多View会增加内存占用。
3.单例里不要持有Activity引用。
4. Bitmap使用低内存的解析模式,如RGB565,对Bitmap进行inSampleSize压缩,在Bitmap不被使用的时候recycle掉。
Android中为什么要进行内存优化 ?
1 避免不合理使用内存导致 GC 次数增多,从而导致应用发生卡顿。
2 降低应用由于内存过大被 LMK 机制杀死的概率。
3 防止应用发生 OOM。