Android性能优化之内存分配

开篇闲话

一个成熟的App必然不能让它发生OOM吧!良好的内存占用也能有效提高App性能

技术详情

下面我们从几个方面学习

内存分配
内存回收

一、内存分配

  • Jvm(Java虚拟机)内存模型
    理解Jvm的内存分配之前先了解下Jvm的内存模型
Jvm(java虚拟机)主要管理两种类型的内存:堆和非堆
堆是运行时数据区域,所有类实例和数组的内存均从此分配
非堆是JVM留给自己用的,包括方法区、JVM内部处理或优化所需的内存(如 JIT  Compiler,Just-in-time Compiler,即时编译后的代码缓存)、每个类结构(如运行时常量池、字段和方法数据)以及方法和构造的代码

简而言之,java程序内部主要分两部分,堆和非堆。一般new的对象和数组都是在堆中的,而GC主要回收的内存也是这个堆内存
  • 堆内存模型
    既然重点是堆内存,我们就再看看堆的内存模型
堆内存是由垃圾回收器的自动内存回收管理系统回收
堆内存分为两大部分:新生代和老年代。比例为1 : 2
老年代主要存放应用程序中生命周期长的存活对象
新生代又分为三个部分:一个Eden和两个Survivor区,比例为8 : 1 : 1
Eden去存放新生的对象
Survivor存放每次垃圾回收后存活的对象

看晕了吧,关注这几个问题:

为什么要分新生代和老年代?
新生代为什么分一个Eden区和两个Survivor区?
一个Eden区和两个Survivor区的比例为什么是8:1:1

这里不做解释看原文
理解Android Java垃圾回收机制

二、内存回收

垃圾回收是如何工作的?
其实,GC最主要的一个流程是:先根据一定的算法判定某个对象是否存活,然后把判定是垃圾的对象进行回收。

1.可回收对象判定
2.通过算法回收内存垃圾

1. 可回收对象的判定

目前,市面上存在有两种算法来判断一个对象是否垃圾

1.引用计数算法
1.首先给每一个对象都添加一个引用计数器

2.当程序的某一个地方引用了此对象,这个计数器的值就加1

3.当引用失效的时候(例如超过了作用域),这个计数器就减1

4.当某一个对象的计数器的值是0的时候,则判定这个对象不可能被使用

这个算法对于系统来说简单,高效,垃圾回收器运行较快,缺点是不能处理循环引用,这就导致互相引用的对象无法被回收


循环引用.png
2.GC Root可达性分析算法

这个算法的工作原理是:

1.以称作“GC Root”的对象作为起点向下搜索
2、没走过一个对象,就生成一条引用链
3、从根开始到搜索完,生成一颗引用树,那些GC Root不可达的对象就是可以被回收的

这个算法明显的解决了循环引用的问题,但是这个算法稍微有点复杂,性能上没有引用计数好


比较.png
比较2.png

文字说明:

1.若使用引用计数算法判定,图中的C和D对象互相引用,导致计数器不为0,无法被回收掉
2.若使用可达性分析算法,C和D对象GC Roots不可达,则能够被回收

我们程序中能够被用来当做GC Root对象的有:

1.虚拟机栈(栈帧中的本地变量表)中引用的对象
2.方法区中静态属性引用的对象
3.方法区中常量引用的对象
4.本地方法栈中JNI引用的对象

关于对象可回收的判定,我们还需要注意的是,当系统开始启动GC的时候,为了保证引用链的状态不变,就需要停止该进程中所有的程序(stop The World),我们Android中的现象就是UI卡顿了,所有我们要避免频繁的产生GC

还值得注意的是,不可达对象也并不是立即就被回收了,还需要两次的标记过程后才能被真正回收

1.如果对象与GC Root没有连接的引用链,就会被第一次标记,随后判定该对象是否有必要执行finalize()方法
2.如果有必要执行finalize()方法,则这个对象就会被放到F-Queue的队列中,稍后由虚拟机建立低优先级的Finalzer线程去执行,但并不承诺等待它运行结束(对象类中能够重写finalize()方法进行自救,但系统最多只能执行一次)
3.如果没有必要执行finalize()方法,则第二次标记

2. 通过算法回收内存垃圾

以上内容判断了某一个对象是否是垃圾,是否应该被回收,接着,系统就要开始回收了,系统的垃圾回收算法有一下几种:

1.标记清除算法(Mark-Sweep)
2.复制算法(Copying)
3.标记整理算法(Mark-Compact)
4.分代回收算法
1.标记清除算法(Mark-Sweep)

顾名思义就先标记出垃圾,然后进行清除


标记清除.png

从图中可以看出:

这个算法的优点是易于理解,容易实现,只需要将特定地址的空间进行处理
缺点比较明显,会形成很对碎片化的内存,在分配大内存对象时,无法申请到足够的空间,从而更多次的出发GC
2.复制算法(Copying)

复制算法,是对标记清除算法而导致的内存碎片化的一个解决方案,原理如图


复制算法.png
1.如图所示,复制算法会将内存平均分为两个区域:区域A 区域B
2.清理区域A时,将A中存活的对象复制到区域B中
3.然后将区域A的所有对象都清除掉,这样A区域就是一个完整的内存块了,避免了碎片化内存

但是这个算法的缺点也很明显,将内存平均分为两个区域,意味着真正使用的内存变成了一半

3.标记整理算法(Mark-Compact)

标记整理算法也是对于标记清除的一个算法,工作原理是:


标记整理.png
1.如图所示,第一步也需要进行存活对象的一个标记,这一步与标记清除算法一模一样

2.将存活的对象向一端移动,例如图中是往左上角那一端进行移动

3.然后把另一端的内存进行清理

从图上看这个算法也能避免内存碎片化,但是相较于复制算法,多了一步效率很低的标记过程,而与标记清除算法相比,多了一步内存整理(往一端移动)的过程,效率上明显要低。

毕竟世界是公平的,任何算法都有两面性,我们开发者只能具体情况具体分析,使用最适合的算法。因此标记整理算法从图中可以看出,这个算法适合存活对象多的,回收对象少的情况。

4.分代回收算法

鉴于以上三种算法都存在自己的缺陷,然后大神们就提出了根据不同对象的不同特征,使用不同的算法进行处理,所以严格来讲并不是一个新的算法,而是属于一个算法的整合方法,我们知道:

1.复制算法适用于存活对象少,回收对象多的情况
2.标记整合算法适用于存活对象多,回收对象少的情况

这两种算法刚好互补,因此只要将这两个算法作用于不同特征的对象就完美了。。。

了解堆的内存模型之后我们知道,对内存中的新生代有很多的垃圾需要回收,老年代只有很少的垃圾需要回收,那么刚好能根据这个特点使用不同的算法回收:

1.对于新生代区域采用复制算法,回收大量的数据就意味着只需要复制较少的数据
2.老年代区域的特点是只能回收较少的对象,所以使用标记整合算法非常合适

通过以上方式,使得GC的整个过程达到了最高效的状态

这里需要注意的是,分代回收算法中的复制算法并不是之前说的 1 : 1 平均分配,而是根据Eden:Survivor A:Survivor B= 8:1:1

复制算法适用于大量回收,少量存活的情况。如果还是 1 : 1 分配就不合理了,我们只需要使用一小块内存来存放存活的对象即可,所以最后就形成两小一大的样式

具体的过程是(简化版):

1.新创建一个对象,默认是分在Eden区域,当Eden区域内存不够的时候,会触发一次Min
or GC(新生代回收),这次回收将存活的对象放到Survivor A区域,然后新的对象继续放在Eden区域

2.再创建一个新的对象,要是Eden区域又不够了,再次触发Minor GC,这个时候会把Eden区域的存活对象以及
Survivor A区域的存活的对象移动到Survivor B区域,然后清空Eden区域以及Survivor A区域

3.如果继续有新的对象创建,不断触发Minor GC,有些对象就会不断在Survivor A区域以及Survivor B区域
来回移动,但移动次数超过15次后,这些对象就会被移动到老年代区域

4.如果新的对象在Minor GC后还是放不下,就直接放到老年代

5.如果Survivor区域放不下该对象,这直接放到老年代

6.如果老年代也满了,就会触发一次Full GC(major gc)


干货总结

掌握好GC策略和原理,对于我们编码来说能够避免一些不必要的内存泄露

本文参考以下博客:
理解Android Java垃圾回收机制

Android性能调优篇之探索垃圾回收机制

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容