引言
接 App 优化之内存优化(序), 作为 App 优化系列中内存优化的一个小部分.
由于内存相关知识比较生涩, 内存优化中使用到的相关工具, 也有很多专有名词. 对 Java 内存管理, GC, Android 内存管理, Dalvik/ART 等知识有一个理论的认识, 可以让我们更好的使用这些工具, 分析内存问题.
据此, 我们就先从理论入手, 聊聊 GC 那些事儿.
1, 何为 GC
GC 是 garbage collection 的缩写, 垃圾回收的意思. 也可以是 Garbage Collector, 也就是垃圾回收器.
1.1 垃圾回收器
我们先来解释下 Garbage Collector(垃圾回收器).
内存管理, 一直是编程中的一个大的问题. 在较老的语言中, 例如 C++ 语言中, 内存管理是显式的, 也就是说使用者自己申请内存使用, 自己释放内存. 这就是为什么 C++ 语言中除了构造函数, 还有析构函数. 我们在创建对象的时候调用构造函数创建, 系统会在对象结束其作用域的时候调用析构函数, 我们需要做的就是在析构函数中释放掉我们申请的相关资源, 以便释放内存地址.
显然, 这种显式的由编程人员自己控制释放内存的方式很容易出问题, 忘了, 漏了, 都可能导致内存问题. 也不符合程序员要懒的特征.
故而, Java 语言中引入了自动内存管理的机制, 也就是垃圾回收器. 大部分的现代面向对象语言, 也都是采用自动内存管理机制.
内存自动管理回收机制可以解决大部分, 但不是所有的内存问题, 这也是为什么我们要讨论内存泄露.
垃圾回收器的职责
垃圾回收器有三大职责:
分配内存;
确保任何被引用的对象保留在内存中;
回收不能通过引用关系找到的对象的内存.
垃圾回收的一般流程
gc process
1.2 相关概念
垃圾回收(GC)
垃圾回收器中有一个进程来做上面的这些事情, 这个进程查找我们的对象引用的关系并释放其内存, 这个进程就是 garbage collection(垃圾回收), 也就是我们常说的 GC.
Heap 和 Stack
简单说下:
Heap 内存是指 java 运行环境用来分配给对象和 JRE 类的内存. 是应用的内存空间.
Stack 内存是相对于线程 Thread 而言的, 它保存线程中方法中短期存在的变量值和对 Heap 中对象的引用等.
Stack 内存, 顾名思义, 是类 Stack 方式, 总是后进先出 (LIFO) 的.
我们通常说的 GC 的针对 Heap 内存的. 因为 Stack 内存相当于是随用随销的.
- heap&stack
- GC Root
直译 GC 根, 我们姑且不译了吧.
所谓 GC Root 我们可以理解为是一个 Heap 内存之外的对象, 通常包括但不仅限于如下几种:
System Class 系统 Class Loader 加载的类. 例如 java 运行环境中 rt.jar 中类, 比如 java.util.* package 中的类.
Thread 运行中的线程
JNI 中的本地 / 全局变量, 用户自定义的 JNI 代码或是 JVM 内部的.
Busy Monitor 任何调用了 wait()或 notify()方法, 或是同步化的 (synchronized) 的东西. 可以理解为同步监控器.
Java 本地实例, 还在运行的 Thread 的 stack 中的方法创建的对象.
活对象 / 垃圾
如果这个对象是引用可达的, 则称之为活的(live), 反之, 如果这个对象引用不可达, 则称之为死的(dead), 也可以称之为垃圾(garbage).
这个引用可达与不可达就是相对于 GC Root 来说的:
gc-roots
2, Java 的内存管理机制
2.1 关于 JVM
我们平常在查看我们的 java 版本时, 你会发现:
- $ java -version
- java version "1.8.0_74"
- Java(TM) SE Runtime Environment (build 1.8.0_74-b02)
- Java HotSpot(TM) 64-Bit Server VM (build 25.74-b02, mixed mode)
其中有个 HotSpot VM 的东西, 那么这个是什么呢? 和 JVM 有什么关系呢?
在此简单说下, 以便行文:
JVM, Java 虚拟机, 可以简单理解为一种技术思想, 虚拟技术理念.
HotSpot VM 是 JVM 的一种实现, 包含了服务器版和桌面应用程序版, 现时由 Oracle 维护并发布.
我们当前使用的 sun(oracle)的 java 版本 (应该是 1.3 以上) 都是内置的 HotSpot VM 实现. 所以接下来的分析也都是基于 HotSpot VM 的, 但是还是简称 JVM.
2.2 JVM 内存区域
JVM 使用分代式的内存管理方式, 将 Heap 分成三代 --- 新生代, 老一代, 持久代.
- Hotspot heap structure
- Young Generation
新生代.
所有 new 的对象.
该区域的内存管理使用 minor garbage collection(小 GC).
更进一步分成 Eden space, Survivor 0 和 Survivor 1 三个部分.
Old Generation
老年区.
新生代中执行小粒度的 GC 幸存下来的 "老" 对象.
该区域的内存管理使用 major garbage collection(大 GC).
Permanent Generation
持久代.
包含应用的类 / 方法信息, 以及 JRE 库的类和方法信息.
小 GC 执行非常频繁, 而且速度特别快.
大 GC 一般会比小 GC 慢十倍以上.
大小 GC 都会发出 "Stop the World" 事件, 也就是说中断程序运行, 直至 GC 完成. 这也是我们在 App 优化之消除卡顿中为什么说频繁 GC 会造成用户感知卡顿.
3, GC 的流程
了解了内存 Heap 的几个区域, 我们再来看下垃圾收集器是怎么利用这几个区域来管理内存和回收垃圾的.
1. 创建新的对象
每当我们使用 new 创建一个对象时, 这个对象会被分配到新生代的 Eden 区域:
object allocation
2. 当 Eden 区域满时
当 Eden 区域内存被分配完时, 小 GC 程序被触发:
Eden filling
引用可达的对象会移到 Survivor(幸存者)区域 --S0, 然后清空 Eden 区域, 此时引用不可达的对象会直接删除, 内存回收, 如下:
aged
3. Eden 再次满时
当 Eden 区域再次分配完后, 小 GC 执行, 引用可达的对象会移到 Survivor(幸存者)区域, 而引用不可达的对象会跟随 Eden 的清空而删除回收.
需要注意的是, 这次引用可达的对象移动到的是 S1 的幸存者区.
而且, S0 区域也会执行小 GC, 将其中还引用可达的对象移动到 S1 区, 且年龄 + 1. 然后清空 S0, 回收其中引用不可达的对象.
此时, 所有引用可达的对象都在 S1 区, 且 S1 区的对象存在不同的年龄. 如下:
next filling
当 Eden 第三次满时, S0 和 S1 的角色互换了:
s0s1
依此循环.
4. 当 Survivor 区的对象年龄达到 "老年线" 时
上面 1~3 循环, Survivor 区的对象年龄也会持续增长, 当其中某些对象年龄达到 "老年线", 例如 8 岁时, 它们会 "晋升" 到老年区.
old aged
如此 1~4 步重复, 大体流程是这样的
gc flow
来源: http://www.bubuko.com/infodetail-2506898.html