目录
1、内存详情
1.1、内存溢出
1.2 、内存泄漏
1.3、内存抖动
2、垃圾回收机制
2.1、垃圾回收算法(标记--清除)
2.2、垃圾回收算法(标记--整理)
2.3、复制算法
2.4、分代回收算法
3.GCRoot原理
3.1、可达性分析法
3.2、引用计数法
1、内存详情
1.1、内存溢出
内存溢出: 指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory
Android系统为每个应用程序申请到的内存有限,一般为64M或者128M等,我们可以在清单文件中进行配置,android:largeheap = "true" 从而给APP申请更大的内存空间;给APP申请更大的内存空间。
内存溢出又分为 : 堆内存溢出, 栈内溢出。
虚拟机: JVM的作用是把平台无关的.class里面的字节码翻译成平台相关的机器码,来实现跨平台。Dalvik和Art就是安卓中使用的虚拟机。
项目中出现内存溢出几种情况:
- 1、代码中存在死循环或递归调用没有退出机制 。
- 2、有大循环重复产生新对象实体。
- 3、List、MAP等集合对象一次性加载数据过多。
- 4、List、MAP等集合对象是否有使用完后,未清除的问题,使得存储的对象有引用一直无法被GC 问题。
- Android开发中应对内存溢出的几种方法:
- 1.、大量位图加载。
- 2、位图对象没有及时释放。
- 3、查询数据库没有及时关闭游标。
- 4、使用Adapter时候没有缓存convertView。
- 5、采用二级缓存机制
- 6、 等比例缩小位图文件
- 7、优化虚拟机堆内存
- 8、强制回收内存信息。System.gc()
1.2 、内存泄漏
内存泄漏:指程序在申请内存后,被某个对象一直持有,无法释放已申请的内存空间。
一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。单例模式在Android开发中会经常用到,但是如果使用不当就会导致内存泄露。因为单例的静态特性使得它的生命周期同应用的生命周期一样长,如果一个对象已经没有用处了,但是单例还持有它的引用,那么在整个应用程序的生命周期它都不能正常被回收,从而导致内存泄漏。
简单的内存泄漏检测方法: 在重复打开关闭某一个Activity时候,发现内存占比一直在攀升,说明有些应该释放的内存没有及时释放。
Android常见的几种内存泄漏:
1、 静态变量导致内存泄漏。静态变量存储在方法区,生命周期从类加载开始到整个进程结束。 一但静态变量初始化后,它所持有的引用只有等待进程结束才会释放。
2、非静态内部类导致内存泄漏 。 非静态内部类包括匿名内部类默认持有外部类引用,当非静态内部类对象生命周期比外部对象的生命周期长时,就会导致内存泄漏。 Android在Handler,Thread 使用上最容易导致内存泄漏。
3、未取消注册或回调导致内存泄漏。 比如在Activity中注册广播,但是在Activity销毁后不取消注册,那么广播系统会一直存在系统中,且引用着Activity,从而导致内存泄漏。
4、集合中对象未清理导致内存泄漏。 假设某个对象被添加到了集合中,也就是集合引用了这个对象,此时这个对象是无法被GC的。
5、资源未关闭或者释放导致内存泄漏。 在使用流或者资源时要及时关闭,资源读写操作通常都是使用了缓冲,这些缓冲对象就会一直被占用得不到释放,以致于内存泄漏。
1.3、内存抖动
内存抖动: 是指在短时间内有大量的对象被创建或者被回收的现象,内存抖动出现原因主要是频繁(很重要)在循环里创建对象(导致大量对象在短时间内被创建,由于新对象是要占用内存空间的而且是很频繁。
Android内存抖动解决方法
1、尽量避免在循环体内创建对象,应该把对象创建移到循环体外。
2、自定义View的onDraw()方法会被频繁调用,所以在这里面不应该频繁的创建对象
3、当需要大量使用Bitmap的时候,试着把它们缓存在数组中实现复用
4、对于能够复用的对象,同理可以使用对象池将它们缓存起来
2、垃圾回收机制
jvm与Android关系
JVM以Class为执行单元,Android虚拟机以Dex执行单元,编译流程JVM直接通过Javac即可加载。Android虚拟机需要先编译成dex,然后编译成apk。最后执行Android Art虚拟机在安装的时候讲dex缓存本地机器码,安装比较慢,耗存储空间。 Android Dalvik虚拟机在程序运行过程中进行翻译。节省空间,耗cpu时间。以空间换时间的典型
2.1、垃圾回收算法(标记--清除)
GC分为2个阶段
标记和清除,首先标记所有可回收的对象,在标记完成后统一回收所有被标记的对象,会产生不连续的内存碎片。 碎片过多会导致以后程序运行时需要分配较大对象是,无法找到足够的连续内存,而不得已再次触发GC,或者直接在开辟新的连续内存,导致内存浪费。
标记阶段: 垃圾回收器会从mutator(应用程序)根对象开始遍历。每一个可以从根对象访问到的对象都会被添加一个标识,于是这个对象就被标识为可到达对象 。
清除阶段:垃圾回收器,会对堆内存从头到尾进行线性遍历,如果发现有对象没有被标识为可到达对象,那么就将此对象占用的内存回收,并且将原来标记为可到达对象的标识清除,以便进行下一次垃圾回收操作 。
特别说明:在垃圾回收阶段,应用程序的执行会暂停,等待回收执行完毕后,再恢复程序的执行。
缺点: 执行不稳定,内存空间碎片化问题。
2.2、垃圾回收算法(标记--整理)
标记—整理算法(回收老年代使用此方法)定义:
标记:标记出所有需要回收的对象/或标记存活的对象。
整理:让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存。
区别:
标记-清除算法是一种非移动式的回收算法。
标记—整理算法是移动式的。
缺点:如果移动存活对象,尤其是在老年代这种每次回收都有大量对象存活区域,移动存活对象并更新。所有引用这些对象的地方将会是一种极为负重的操作;而且这种对象移动操作必须全程暂停用户应用程序才能进行。像这样的停顿被最初的虚拟机 设计者形象地描述为“Stop The World”(Stop The World非常影响系统的响应时间)。
2.3、复制算法
原理:将内存空间分为两块,每次只使用其中一块,在垃圾回收的时候,将正在使用的内存中的存活对象复制到未被使用的内存块中,之后清除正在使用的内存块,交换两个内存角色。
缺点:
1、需要两倍空间。
2、GC需要维护对象的引用关系,时间开销加大此种方案使用与垃圾对象较少,量级不大的情况
2.4、分代回收算法
为了满足垃圾回收的效率最优性,分代手机算法基于一个事实:不同的对象生命周期是不一样的,因此,不同生命周期的对象可以采取不同的手机方式,以便于提高回收效率。
一般是把JAVA堆分为新生代和老年代,这样就可以根据各个年代的特点使用不同回收算法,相对提高效率在系统运行过程汇总,会产生大量对象,其中有些对象是业务信息相关,如HTTP请求的Session、线程、Socket连接等对象,这类对象跟业务挂钩,因此生命周期长,还有一部分是运行过程汇总生成的临时变量,这些对象生命周期短,比如:String,这些对象甚至只使用一次即可回收 。
目前所有GC都采用分代收集算法进行执行
对象的状态经过大量的调研研究划分为年青代与老年代两个类别
(1)、年轻代:区域相对小,对象生命周期短、存活率低,且产生应用频繁
复制算法回收整理速度是最快的。复制算法效率只与当前存活对象大小有关,因此很实用与年青代的回收,而空间问题,因为存活率问题,所以单独开辟S0,S1两块空间处理清除后结果
(2)、老年代:区域较大,生命周期长、存活率高,回收不及年青代频繁 。
3.GCRoot原理
1、虚拟机栈(javaStack)(栈帧中的局部变量区,也叫做局部变量表)中引用的对象。
2、方法区中的类静态属性引用的对象。
3、方法区中常量引用的对象。
4、本地方法栈中JNI(Native方法)引用的对象。
3.1、可达性分析法
相对于引用计数算法,他有效的解决了在引用计数算法中的循环引用问题,防止内存泄漏发生
这种类型的垃圾收集也叫作追踪性垃圾收集
概念:
(1)可达性分析算法以跟对象集合为起点,按照从上至下的方式搜索被跟对象集合所链接的对象目标是否可达
(2)使用可达性分析算法后,内存中的存货对象会被跟对象集合直接或者间接连接着,搜索所走过的路径称之为引用链
(3)如果目标对象没有任何阴影链项链,则是不可达的,意味着该对象已经死亡,可以标记为垃圾对象。
在可达性分析算法中只有能够被根对象集合直接或间接连接的对象才是存活对象
3.2、引用计数法
原理:对每一个对象保存一个整形的引用计数器属性,用于记录对象被引用的情况。
优点:实现简单,垃圾对象便于识别,判断效率高
缺点:他需要单独的字段存储计数器,这样的做法增加的存储空间的开销
每次赋值需要额外的加减法计算,增加了时间开销
引用计数算法最大的问题是无法处理循环引用的情况,这是一个比较致命的缺陷
参考文章:
JVM10_引用计数法、GCROOT、Finalization机制、复制、标记清除、标记压缩算法、分代收集、增量收集、分区算法(一)-阿里云开发者社区
内存泄漏_百度百科
Java垃圾回收机制(GC原理)解析_分代手机算法_浮空over的博客-CSDN博客