<commit_before>---
layout: post
title:  "Android内存优化!"
date:   2018-02-13 13:31:01 +0800
categories: jekyll
tag: jekyll
---

* content
{:toc}


First POST build by Jekyll.


Android内存优化
------------------------
参考：https://www.jianshu.com/p/c4b283848970
##介绍
内存这东西为什么被经常被提及。因为内存条贵呀，前端时间电脑的内存条涨的多凶呀，都买不起了。开个玩笑啦，不过确实内存这东西不便宜，尤其是移动平台上。iphone内存才多大，android稍微大些不过再大还是不够用。作为开发者需要反省下我们到底对内存负责了吗？

内存（RAM）在平常感觉讲的很多，经常就被人写一遍。但是在实际开发中感觉跟内存直接关系不大，这肯定是错误的。开发过程中与内存的关系都在无形之中体现出来。可以说基本所有都与内存牵扯上关系，比如图片、activity、service、资源等等。你的application运行在手机上还不是要根据RAM的大小分配这个application所能占用的最大内存。

总之一句话：内存在开发过程中看起来是由垃圾回收机制进行回收资源的，但是管理的好不好还是靠开发者的功底。因此内存优化经常在面试中被提及。

###内存分配
从Android的系统架构上来看，这内存可以细分下1.方法区
	2.虚拟机栈
	3.本地方法栈
	4.堆
	5.程序计数器,如图：
![name](https://upload-images.jianshu.io/upload_images/4056837-b9917fe65f396180?imageMogr2/auto-orient/strip%7CimageView2/2/w/587 'jvm运行时数据')
	
方法区：方法区存放的是类信息、常量、静态变量，所有线程共享区域。

虚拟机栈：每个方法在执行的同时都会创建一个栈帧（Stack Frame）用于存储局部变量表、操作数栈、动态链接、方法出口等信息，线程私有区域。

本地方法栈：与虚拟机栈类似，区别是虚拟机栈为虚拟机执行Java方法服务，本地方法栈为虚拟机使用到的Native方法服务。

堆：JVM管理的内存中最大的一块，所有线程共享；用来存放对象实例，几乎所有的对象实例都在堆上分配内存；此区域也是垃圾回收器（Garbage Collection）主要的作用区域，内存泄漏就发生在这个区域。

程序计数器：可看做是当前线程所执行的字节码的行号指示器；如果线程在执行Java方法，这个计数器记录的是正在执行的虚拟机字节码指令地址；如果执行的是Native方法，这个计数器的值为空（Undefined）。
###内存回收
1.Android FrameWork:FrameWork会根据进程优先级进行回收。进程优先级依次是：空进程、后台进程、服务进程、可见进程、前台进程。

2.JVM虚拟机：Android为JVM分配了内存大小，Application出现oom就是申请的内存大于当前Application JVM内存的最大值。

3.Linux内核：这里最简单的理解就是ActivityManagerService会对所有进程进行评分（存放在变量adj中），然后再讲这个评分更新到内核，由内核去完成真正的内存回收(lowmemorykiller, Oom_killer)。

补充：Android Dalvik Heap与原生Java一样，将堆的内存空间分为三个区域，Young Generation，Old Generation， Permanent Generation。最近分配的对象会存放在Young Generation区域，当这个对象在这个区域停留的时间达到一定程度，它会被移动到Old Generation，最后累积一定时间再移动到Permanent Generation区域。系统会根据内存中不同的内存数据类型分别执行不同的gc操作。

现在各种手机厂商鼓吹人工智能手机，号称18个月不卡顿，越用越快，其实很大一部分Android系统的内存优化有关，无非就是利用一些比较成熟的基于统计，机器学习的算法定时清理数据，清理内存，甚至提前加载数据到内存。
####内存回收算法
1.内存清除法

分为“标记”和“清除”两个阶段，首先，标记出所有需要回收的对象，然后统一回收所有被标记的对象。

缺点：1.效率问题，标记和清除两个过程的效率都不高；
2.空间问题，标记清除之后会产生大量的不连续的内存碎片。

2.复制算法

将内存按容量划分为大小相等的两块，每次只使用其中的一块，当这一块内存将用完了，就将还存活着的对象复制到另一块内存上面，然后再把已使用过的内存空间一次清理掉。

1.优点：实现简单，运行高效；每次都是对整个半区进行内存回收，内存分配时也不需要考虑内存碎片等情况，只要移动堆顶指针，按顺序分配内存即可；
2.缺点：粗暴的将内存缩小为原来的一半，代价实在有点高。

3.标记-整理算法

先标记需要回收的对象（标记过程与“标记-清除”算法一样），然后把所有存活的对象都向一端移动，然后直接清理掉端边界以外的内存。

特点：

避免了内存碎片；
避免了“复制”算法50%的空间浪费。

####内存回收依据
如何判断内存被回收了呢？

引用计数法：给对象中添加一个引用计数器，每当有一个地方引用该对象时，计数器值加1；引用失效时，计数器值减1；任意时刻计数器为0的对象就是不可能再被使用的，表示该对象不存在引用关系。

优点：实现简单，判定效率也很高；
缺点：难以解决对象之间相互循环引用导致计数器值不等于0的问题。

可达性分析算法：以一系列成为“GC Roots”的对象作为起始点，从这些节点开始向下搜索，搜索所走过的路径称为引用链，当一个对象到GC Roots没有任何引用链相连（GC Roots到这个对象不可达），则证明此对象是不可用的。
![name](https://upload-images.jianshu.io/upload_images/4056837-a0c9dc12c1434bc6?imageMogr2/auto-orient/strip%7CimageView2/2/w/465 '可达性分析')

### 内存管理
Android系统的ART和Dalvik虚拟机扮演了常规的内存垃圾自动回收的角色， 使用paging 和 memory-mapping来管理内存，这意味着不管是因为创建对象还是使用使用内存页面造成的任何被修改的内存，都会一直存在于内存中，App唯一释放内存的方法就是释放App持有的对象引用，使GC可以回收。

##### 内存回收
在Android的高级系统版本里面针对Heap空间有一个Generational Heap Memory的模型，最近分配的对象会存放在Young Generation区域，当这个对象在这个区域停留的时间达到一定程度，它会被移动到Old Generation，最后累积一定时间再移动到Permanent Generation区域。系统会根据内存中不同的内存数据类型分别执行不同的gc操作。例如，刚分配到Young Generation区域的对象通常更容易被销毁回收，同时在Young Generation区域的gc操作速度会比Old Generation区域的gc操作速度更快。

##### 共享内存

Android应用的进程都是从一个叫做Zygote的进程fork出来的。Zygote进程在系统启动并且载入通用的framework的代码与资源之后开始启动。为了启动一个新的程序进程，系统会fork Zygote进程生成一个新的进程，然后在新的进程中加载并运行应用程序的代码。这使得大多数的RAM pages被用来分配给framework的代码，同时使得RAM资源能够在应用的所有进程之间进行共享。

大多数static的数据被mmapped到一个进程中。这不仅仅使得同样的数据能够在进程间进行共享，而且使得它能够在需要的时候被paged out。常见的static数据包括Dalvik Code，app resources，so文件等。

大多数情况下，Android通过显式的分配共享内存区域(例如ashmem或者gralloc)来实现动态RAM区域能够在不同进程之间进行共享的机制。例如，Window Surface在App与Screen Compositor之间使用共享的内存，Cursor Buffers在Content Provider与Clients之间共享内存。
##### 应用切换
Android系统并不会在用户切换应用的时候做交换内存的操作。Android会把那些不包含Foreground组件的应用进程放到LRU Cache中。例如，当用户开始启动了一个应用，系统会为它创建了一个进程，但是当用户离开这个应用，此进程并不会立即被销毁，而是会被放到系统的Cache当中，如果用户后来再切换回到这个应用，此进程就能够被马上完整的恢复，从而实现应用的快速切换。

如果你的应用中有一个被缓存的进程，这个进程会占用一定的内存空间，它会对系统的整体性能有影响。因此当系统开始进入Low Memory的状态时，它会由系统根据LRU的规则与应用的优先级，内存占用情况以及其他因素的影响综合评估之后决定是否被杀掉。<commit_msg>Update 2018-02-13-android内存优化.md<commit_after>---
layout: post
title:  "Android内存优化!"
date:   2018-02-13 13:31:01 +0800
categories: jekyll
tag: jekyll
---

* content
{:toc}


First POST build by Jekyll.


Android内存优化
------------------------
参考：https://www.jianshu.com/p/c4b283848970
##介绍
内存这东西为什么被经常被提及。因为内存条贵呀，前端时间电脑的内存条涨的多凶呀，都买不起了。开个玩笑啦，不过确实内存这东西不便宜，尤其是移动平台上。iphone内存才多大，android稍微大些不过再大还是不够用。作为开发者需要反省下我们到底对内存负责了吗？

内存（RAM）在平常感觉讲的很多，经常就被人写一遍。但是在实际开发中感觉跟内存直接关系不大，这肯定是错误的。开发过程中与内存的关系都在无形之中体现出来。可以说基本所有都与内存牵扯上关系，比如图片、activity、service、资源等等。你的application运行在手机上还不是要根据RAM的大小分配这个application所能占用的最大内存。

总之一句话：内存在开发过程中看起来是由垃圾回收机制进行回收资源的，但是管理的好不好还是靠开发者的功底。因此内存优化经常在面试中被提及。

###内存分配
从Android的系统架构上来看，这内存可以细分下1.方法区
	2.虚拟机栈
	3.本地方法栈
	4.堆
	5.程序计数器,如图：
![name](https://upload-images.jianshu.io/upload_images/4056837-b9917fe65f396180?imageMogr2/auto-orient/strip%7CimageView2/2/w/587 'jvm运行时数据')
	
方法区：方法区存放的是类信息、常量、静态变量，所有线程共享区域。

虚拟机栈：每个方法在执行的同时都会创建一个栈帧（Stack Frame）用于存储局部变量表、操作数栈、动态链接、方法出口等信息，线程私有区域。

本地方法栈：与虚拟机栈类似，区别是虚拟机栈为虚拟机执行Java方法服务，本地方法栈为虚拟机使用到的Native方法服务。

堆：JVM管理的内存中最大的一块，所有线程共享；用来存放对象实例，几乎所有的对象实例都在堆上分配内存；此区域也是垃圾回收器（Garbage Collection）主要的作用区域，内存泄漏就发生在这个区域。

程序计数器：可看做是当前线程所执行的字节码的行号指示器；如果线程在执行Java方法，这个计数器记录的是正在执行的虚拟机字节码指令地址；如果执行的是Native方法，这个计数器的值为空（Undefined）。
### 内存回收
1.Android FrameWork:FrameWork会根据进程优先级进行回收。进程优先级依次是：空进程、后台进程、服务进程、可见进程、前台进程。

2.JVM虚拟机：Android为JVM分配了内存大小，Application出现oom就是申请的内存大于当前Application JVM内存的最大值。

3.Linux内核：这里最简单的理解就是ActivityManagerService会对所有进程进行评分（存放在变量adj中），然后再讲这个评分更新到内核，由内核去完成真正的内存回收(lowmemorykiller, Oom_killer)。

补充：Android Dalvik Heap与原生Java一样，将堆的内存空间分为三个区域，Young Generation，Old Generation， Permanent Generation。最近分配的对象会存放在Young Generation区域，当这个对象在这个区域停留的时间达到一定程度，它会被移动到Old Generation，最后累积一定时间再移动到Permanent Generation区域。系统会根据内存中不同的内存数据类型分别执行不同的gc操作。

现在各种手机厂商鼓吹人工智能手机，号称18个月不卡顿，越用越快，其实很大一部分Android系统的内存优化有关，无非就是利用一些比较成熟的基于统计，机器学习的算法定时清理数据，清理内存，甚至提前加载数据到内存。
#### 内存回收算法
1.内存清除法

分为“标记”和“清除”两个阶段，首先，标记出所有需要回收的对象，然后统一回收所有被标记的对象。

缺点：1.效率问题，标记和清除两个过程的效率都不高；
2.空间问题，标记清除之后会产生大量的不连续的内存碎片。

2.复制算法

将内存按容量划分为大小相等的两块，每次只使用其中的一块，当这一块内存将用完了，就将还存活着的对象复制到另一块内存上面，然后再把已使用过的内存空间一次清理掉。

1.优点：实现简单，运行高效；每次都是对整个半区进行内存回收，内存分配时也不需要考虑内存碎片等情况，只要移动堆顶指针，按顺序分配内存即可；
2.缺点：粗暴的将内存缩小为原来的一半，代价实在有点高。

3.标记-整理算法

先标记需要回收的对象（标记过程与“标记-清除”算法一样），然后把所有存活的对象都向一端移动，然后直接清理掉端边界以外的内存。

特点：

避免了内存碎片；
避免了“复制”算法50%的空间浪费。

#### 内存回收依据
如何判断内存被回收了呢？

引用计数法：给对象中添加一个引用计数器，每当有一个地方引用该对象时，计数器值加1；引用失效时，计数器值减1；任意时刻计数器为0的对象就是不可能再被使用的，表示该对象不存在引用关系。

优点：实现简单，判定效率也很高；
缺点：难以解决对象之间相互循环引用导致计数器值不等于0的问题。

可达性分析算法：以一系列成为“GC Roots”的对象作为起始点，从这些节点开始向下搜索，搜索所走过的路径称为引用链，当一个对象到GC Roots没有任何引用链相连（GC Roots到这个对象不可达），则证明此对象是不可用的。
![name](https://upload-images.jianshu.io/upload_images/4056837-a0c9dc12c1434bc6?imageMogr2/auto-orient/strip%7CimageView2/2/w/465 '可达性分析')

### 内存管理
Android系统的ART和Dalvik虚拟机扮演了常规的内存垃圾自动回收的角色， 使用paging 和 memory-mapping来管理内存，这意味着不管是因为创建对象还是使用使用内存页面造成的任何被修改的内存，都会一直存在于内存中，App唯一释放内存的方法就是释放App持有的对象引用，使GC可以回收。

##### 内存回收
在Android的高级系统版本里面针对Heap空间有一个Generational Heap Memory的模型，最近分配的对象会存放在Young Generation区域，当这个对象在这个区域停留的时间达到一定程度，它会被移动到Old Generation，最后累积一定时间再移动到Permanent Generation区域。系统会根据内存中不同的内存数据类型分别执行不同的gc操作。例如，刚分配到Young Generation区域的对象通常更容易被销毁回收，同时在Young Generation区域的gc操作速度会比Old Generation区域的gc操作速度更快。

##### 共享内存

Android应用的进程都是从一个叫做Zygote的进程fork出来的。Zygote进程在系统启动并且载入通用的framework的代码与资源之后开始启动。为了启动一个新的程序进程，系统会fork Zygote进程生成一个新的进程，然后在新的进程中加载并运行应用程序的代码。这使得大多数的RAM pages被用来分配给framework的代码，同时使得RAM资源能够在应用的所有进程之间进行共享。

大多数static的数据被mmapped到一个进程中。这不仅仅使得同样的数据能够在进程间进行共享，而且使得它能够在需要的时候被paged out。常见的static数据包括Dalvik Code，app resources，so文件等。

大多数情况下，Android通过显式的分配共享内存区域(例如ashmem或者gralloc)来实现动态RAM区域能够在不同进程之间进行共享的机制。例如，Window Surface在App与Screen Compositor之间使用共享的内存，Cursor Buffers在Content Provider与Clients之间共享内存。
##### 应用切换
Android系统并不会在用户切换应用的时候做交换内存的操作。Android会把那些不包含Foreground组件的应用进程放到LRU Cache中。例如，当用户开始启动了一个应用，系统会为它创建了一个进程，但是当用户离开这个应用，此进程并不会立即被销毁，而是会被放到系统的Cache当中，如果用户后来再切换回到这个应用，此进程就能够被马上完整的恢复，从而实现应用的快速切换。

如果你的应用中有一个被缓存的进程，这个进程会占用一定的内存空间，它会对系统的整体性能有影响。因此当系统开始进入Low Memory的状态时，它会由系统根据LRU的规则与应用的优先级，内存占用情况以及其他因素的影响综合评估之后决定是否被杀掉。
{'fraction_non_alphanumeric': 0.0948469006721434, 'fraction_numerical': 0.025889967637540454, 'mean_word_length': 14.601941747572816, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 6, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5886884', 'n_tokens_mistral': 6233, 'n_tokens_neox': 7085, 'n_words': 234}