Android 内存优化(一) - 基础知识

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_31876841/article/details/78724831

内存结构

内存结构

  • 方法区
    线程共享,存储类的信息、常量、静态变量、编译后的代码
  • 堆区
    线程共享,所有对象的实例,数组
  • 栈区
    线程私有,局部变量表,操作栈,动态链接,方法出口,对象引用

内存管理

在 Android 中我们采用的是 paging(分页)memory-mapping(mmapping-内存映射) 机制来管理内存的。通过这种机制我们把数据分成固定大小的区块,当需要时就从硬盘中提取出来,加载到主存中,当不需要时就移回硬盘中。

内存容量

在 Android 中每一个 app 都限定了一个 heap size 的最大值。这个值会因为不同的设备而有所不同。如果 app 到达了这个值后继续申请内存的会就会引起 OutOfMemoryError 错误。我们可以通过

Runtime.getRuntime().maxMemory();

来获取当前 app 可获取的最大的 heap size。
如果扩大这个最大值,可以在 AndroidManifest 的 Application 中设置

android:largeHeap="true"

通过这种方法,就能扩大 heap size 的最大值。

GC(Garbage collection)

在 app 的执行过程中我们会创建很多的对象来实现不同的功能,这么多的对象为什么没有触发 OOM 呢?这是因为 Java 为我们提供了 GC(垃圾回收)机制。它能帮助我们销毁掉不再使用的对象,为其他对象腾出空间。在这里我们讨论的是针对堆区和方法区中的GC,栈区中的空间只要利用栈来管理就行了十分方便。

堆区

判断对象是否可回收

引用计数算法

给每个对象添加一个引用计数器,每有一个对象引用它了,就 +1 ,失效时 -1 。当计数器为 0 的对象就是不再使用的,GC 就会对其进行回收。
这个方法很方便,但是,它很难解决对象相互循环引用的问题,如下图,即使 Instance1 已经与原有引用断开了关系,但是和其他对象形成了互相循环引用,计数器都为1,这样就不能被回。

引用计数法

根搜索法

这个方法利用了有向图的概念,若不能从 GC-Root 到达的对象就为可回收的对象。改,方法就解决了对象相互循环引用的问题,只要 GC-Root 不可达即视为可回收。Java 就是采用了这种方法。

根搜索法

GC-Root :

扫描二维码关注公众号,回复: 5465731 查看本文章
  • 虚拟机栈中局部变量引用的对象
  • 类静态属性引用的对象
  • 常量引用的对象
  • JNI中引用的对象

当检测到对象不可达时,GC 不会立即对其进行回收,而是先对其进行标记。若发现没有执行 finalize 方法,就会让其进入 F-Queue 队列,开启另一个线程执行其 finalize 方法,若执行后发现它添加回 GC-Root 的引用链中了就不再理会,没有则第二次标记,然后回收。执行过 finalize 的则直接第二次标记,然后回收。

GC-Root回收过程

引用的分类

强引用

在代码中普遍存在的

Object obj = new Object();

只要强引用还在,GC 就不会回收掉。当内存不足时,虚拟机宁愿抛出 OOM 也不回收强引用

软引用

在内存足够的情况下不回收,在内存不够用的时候才会回收

SoftReference ref = new SoftReference(new Object());
弱引用

当 GC 发生时就会被回收

WeakReference ref = new WeakReference(new Object());
虚引用

虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。

方法区

回收的内容主要有:废弃的常量和无用的类

  • 废弃常量回收和堆中的对象回收时类似。
  • 无用的类
  • 该类的所有实例都已经卑回收,即堆区中无该类的任何实例
  • 该类的 ClassLoader 都已经被回收
  • 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

内存回收算法

标记清除(Mark-Sweep)

  • 标记:标记出不可达对象
  • 清除:回收标记的对象
  • 优点:简单,易实现
  • 确定:容易产生内存碎片
    适用于小对象

复制算法(Copying)

把内存区域分为容量相等的两块区域,每次只使用其中一块,当一块内存空间用完了,就把存活的对象复制到另一块中,然后把刚使用过的内存空间一次清除
- 优点:简单,不会有碎片
- 缺点:空间使用率低,当存活对象多时,效率会降低。

标记整理算法(Mark-Compact)

与 Mark-Sweep 相似,只是标记后,对存活的对象进行了整理,把他们向一端移动,然后清除掉端边界以外的内存。
适合存活对象多,回收对象少的情况。

分代(Generation)

结合了 Copying 和 Mark-Compact .Java 就是采用了分代收集法。

generation

  • 年轻代(Young Generation)
    新 new 出来的对象就会分配到这里,采用 minor garbage collection(Copying)
    1. 新 new 一个对象时,这个对象会分配到 Eden
    2. 当 Eden 满了的时候引用可达的对象就会进入 S0(Survivor),然后清空 Eden
    3. 当 S0 也满了后,将 S0 中引用可达的对象移入 S1 ,请空 S0 。当两个存活区切换了几次后,任存活的对象复制进老年代
    4. Eden:S0:S1 = 8:1:1

  • 老年代(Old Generation)
    老年代空间会比年轻代要大,GC(Major GC|Full GC - Mark-Compact) 发生的次数也比年轻代少。大对象可直接进入老年代。采用标记整理法。

  • 永久代(Permanent Generation)
    包含应用的类/方法信息,以及 JRE 库的类和方法信息

Stop-The-World

总的来说,GC 对于内存的管理来说是十分有好处的。但是,在 GC 的过程中会暂停主线程(我们称为 stop-the-world)。我们知道,Android 所有 UI 的刷新都是在主线程上的,如果 GC 的时长过长,或频繁的GC(内存抖动)就会导致 UI 的刷新的过程卡顿,超过 16ms 这时用户就会感觉到界面的卡顿。

猜你喜欢

转载自blog.csdn.net/qq_31876841/article/details/78724831