Android性能优化--内存篇

看到很多关于内存优化的博客好文,也收藏了好多地址,但每次看时都需要在众多收藏的地址里寻找一番,不如写到自己的博客里,方便随时翻看。
内存优化也是android进阶的必学内容。APP内存的使用,是评价一款应用性能高低的一个重要指标。

1.内存与内存分配策略概述

1.1 什么是内存

通常情况下我们说的内存是指手机的RAM,它主要包括一下几个部分:
- 寄存器(Registers读音:[ˈrɛdʒɪstɚ])
速度最快的存储场所,因为寄存器位于处理器内部,所以在程序中我们无法控制。
- 栈(Stack)
存放基本类型的对象和引用,但是对象本身不存放在栈中,而是存放在堆中。

变量其实是分为两部分的:一部分叫变量名,另外一部分叫变量值,对于局部变量(基本类型的变量和对象的引用变量)而言,统一都存放在栈中,但是变量值中存储的内容就有在一定差异了:Java中存在8大基本类型,他们的变量值中存放的就是具体的数值,而其他的类型都叫做引用类型(对象也是引用类型,你只要记住除了基本类型,都是引用类型)他们的变量值中存放的是他们在堆中的引用(内存地址)。

在函数执行的时候,函数内部的局部变量就会在栈上创建,函数执行结束的时候这些存储单元会被自动释放。栈内存分配运算内置于处理器的指令集中是一块连续的内存区域,效率很高,速度快,但是大小是操作系统预定好的所以分配的内存容量有限。
- 堆(Heap)
在堆上分配内存的过程称作 内存动态分配过程。在java中堆用于存放由new创建的对象和数组。堆中分配的内存,由java虚拟机自动垃圾回收器(GC)来管理(可见我们要进行的内存优化主要就是对堆内存进行优化)。堆是不连续的内存区域(因为系统是用链表来存储空闲内存地址,自然不是连续的),堆大小受限于计算机系统中有效的虚拟内存(32bit系统理论上是4G)。
- 静态存储区/方法区(Static Field)
是指在固定的位置上存放应用程序运行时一直存在的数据,java在内存中专门划分了一个静态存储区域来管理一些特殊的数据变量如静态的数据变量。
- 常量池(Constant Pool)
顾名思义专门存放常量的。注意 String s = “java”中的“java”也是常量。JVM虚拟机为每个已经被转载的类型维护一个常量池。常量池就是该类型所有用到地常量的一个有序集合包括直接常量(基本类型,String)和对其他类型、字段和方法的符号引用。

总结:

定义一个局部变量的时候,java虚拟机就会在栈中为其分配内存空间,局部变量的基本数据类型和引用存储于栈中,引用的对象实体存储于堆中。因为它们属于方法中的变量,生命周期随方法而结束。
成员变量全部存储与堆中(包括基本数据类型,引用和引用的对象实体),因为它们属于类,类对象终究是要被new出来使用的。当堆中对象的作用域结束的时候,这部分内存也不会立刻被回收,而是等待系统GC进行回收。
所谓的内存分析,就是分析Heap中的内存状态。

1.2 Android中的沙盒机制

大家可能都听说过iOS中有沙盒机制(sandbox),但是我们的Android系统中也存在沙盒机制,只不过没有ios中的严格,所以常常被人忽略。

由于Android是建立在Linux系统之上的,所以Android系统继承了linux的 类Unix继承进程隔离机制与最小权限原则,并且在原有Linux的进程管理基础上对UID的使用做了改进,形成了Android应用的”沙箱“机制。

普通的Linux中启动的应用通常和登陆用户相关联,同一用户的UID相同。但是Android中给不同的应用都赋予了不同的UID,这样不同的应用将不能相互访问资源。对应用而言,这样会更加封闭,安全。

在Android系统中,应用(通常)都在一个独立的沙箱中运行,即每一个Android应用程序都在它自己的进程中运行,都拥有一个独立的Dalvik虚拟机实例。Dalvik经过优化,允许在有限的内存中同时高效地运行多个虚拟机的实例,并且每一个Dalvik应用作为一个独立的Linux进程执行。Android这种基于Linux的进程“沙箱”机制,是整个安全设计的基础之一。

简单点说就是在Android的世界中每一个应用相当与一个Linux中的用户,他们相互独立,不能相互共享与访问,(这也就解释了Android系统中为什么需要进程间通信),正是由于沙盒机制的存在最大程度的保护了应用之间的安全,但是也带来了每一个应用所分配的内存大小是有限制的问题。

2.Generational Heap Memory内存模型的概述

在Android和Java中都存在着一个Generational(读音:[ˌdʒenəˈreɪʃənl]) Heap Memory模型,系统会根据内存中不同的内存数据类型分别执行不同的GC操作。Generational Heap Memory模型主要由:Young Generation(新生代)、Old Generation(旧生代)、Permanent(读音:[ˈpɜ:rmənənt]) Generation三个区域组成,而且这三个区域存在明显的层级关系。所以此模型也可以成为三级Generation的内存模型。
这里写图片描述
Generational Heap Memory的模型

其中Young Generation区域存放的是最近被创建对象,此区域最大的特点就是创建的快,被销毁的也很快。当对象在Young Generation区域停留的时间到达一定程度的时候,它就会被移动到Old Generation区域中,同理,最后他将会被移动到Permanent Generation区域中。
这里写图片描述
三级内存模型的移动

在三级Generation内存模型中,每一个区域的大小都是有固定值的,当进入的对象总大小到达某一级内存区域阀值的时候就会触发GC机制,进行垃圾回收,腾出空间以便其他对象进入。
这里写图片描述
触发GC机制

不仅如此,不同级别的Generation区域GC是需要的时间也是不同的。同等对象数目下,Young Generation GC所需时间最短,Old Generation次之,Permanent Generation 需要的时间最长。当然GC执行的长短也和当前Generation区域中的对象数目有关。遍历查找20000个对象比起遍历50个对象自然是要慢很多的。

3.GC机制概述

与C++不用,在Java中,内存的分配是由程序完成的,而内存的释放是由垃圾收集器(Garbage Collection,GC)完成的,程序员不需要通过调用函数来释放内存,但也随之带来了内存泄漏的可能。简单点说:对于 C++ 来说,内存泄漏就是new出来的对象没有 delete,俗称野指针;而对于 java 来说,就是 new 出来的 Object 放在 Heap 上无法被GC回收。

Android使用的主要开发语言是Java所以二者的GC机制原理也大同小异,所以我们只对于常见的JVM GC机制的分析,就能达到我们的目的。我还是先看看那二者的不同之处吧。

3.1 Dalvik 和标准Java虚拟机的区别

3.1.1 Dalvik 和标准Java虚拟机的主要区别

Dalvik虚拟机(DVM)是Android系统在java虚拟机(JVM)基础上优化得到的,DVM是基于寄存器的,而JVM是基于栈的,由于寄存器高效快速的特性,DVM的性能相比JVM更好。

3.1.2 Dalvik 和 java 字节码的区别

Dalvik执行.dex格式的字节码文件,JVM执行的是.class格式的字节码文件,Android程序在编译之后产生的.class 文件会被aapt工具处理生成R.class等文件,然后dx工具会把.class文件处理成.dex文件,最终资源文件和.dex文件等打包成.apk文件。

3.2 分别对Young Generation(新生代)和Old Generation(旧生代)采用的两种垃圾回收机制?

3.2.1 对于Young Generation(新生代)的GC

由于Young Generation通常存活的时间比较短,所以Young Generation采用了Copying算法进行回收,Copying算法就是扫描出存活的对象,并复制到一块新的空间中,这个过程就是下图Eden与Survivor Space之间的复制过程。Young Generation采用空闲指针的方式来控制GC触发,指针保存最后一个分配在Young Generation中分配空间地对象的位置。当有新的对象要分配内存空间的时候,就会主动检测空间是否足够,不够的情况下就出触发GC,当连续分配对象时,对象会逐渐从Eden移动到Survivor,最后移动到Old Generation。
这里写图片描述
Generational Heap Memory的模型

3.2.2 对于Old Generation(旧生代)的GC

Old Generation与Young Generation不同,对象存活的时间比较长,比较稳固,因此采用标记(Mark)算法来进行回收。所谓标记就是扫描出存活的对象,然后在回收未必标记的对象。回收后的剩余空间要么进行合并,要么标记出来便于下次进行分配,总之就是要减少内存碎片带来的效率损耗。

3.4 如何判断对象是否可以被回收

从上面的一小节中我们知道了不同的区域GC机制是有所不同的,那么这些垃圾是如何被发现的呢?下面我们就看一下两种常见的判断方法:引用计数、对象引用遍历。

3.4.1引用计数器

引用计数器是垃圾收集器中的早起策略。这种方法中,每个对象实体(不是它的引用)都有一个引用计数器。当一个对象创建的时候,且将该对象分配给一个每分配给一个变量,计数器就+1,当一个对象的某个引用超过了生命周期或者被设置一个新值时,对象计数器就-1,任何引用计数器为 0 的对象可以被当作垃圾收集。当一个对象被垃圾收集时,引用的任何对象技术 - 1。
优点:执行快,交织在程序运行中,对程序不被长时间打断的实时环境比较有利。
缺点:无法检测出循环引用。比如:对象A中有对象B的引用,而B中同时也有A的引用。

3.4.2 跟踪收集器

现在的垃圾回收机制已经不太使用引用计数器的方法判断是否可回收,而是使用跟踪收集器方法。

现在大多数JVM采用对象引用遍历机制从程序的主要运行对象(如静态对象/寄存器/栈上指向的堆内存对象等)开始检查引用链,去递归判断对象收否可达,如果不可达,则作为垃圾回收,当然在便利阶段,GC必须记住那些对象是可达的,以便删除不可到达的对象,这称为标记(marking)对象。

下一步,GC就要删除这些不可达的对象,在删除时未必标记的对象,释放它们的内存的过程叫做清除(sweeping),而这样会造成内存碎片化,布局已分配

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值