美文网首页AndroidAPP的优化android技术专栏
GC那些事儿--Android内存优化第一弹

GC那些事儿--Android内存优化第一弹

作者: anly_jun | 来源:发表于2016-10-14 12:01 被阅读12241次
    内容太晦涩,先乐一个

    引言

    App优化之内存优化(序), 作为App优化系列中内存优化的一个小部分.

    由于内存相关知识比较生涩, 内存优化中使用到的相关工具, 也有很多专有名词. 对Java内存管理, GC, Android内存管理, Dalvik/ART等知识有一个理论的认识, 可以让我们更好的使用这些工具, 分析内存问题.

    据此, 我们就先从理论入手, 聊聊GC那些事儿.

    1, 何为GC

    GC 是 garbage collection 的缩写, 垃圾回收的意思. 也可以是 Garbage Collector, 也就是垃圾回收器.

    1.1 垃圾回收器

    我们先来解释下Garbage Collector(垃圾回收器).

    内存管理, 一直是编程中的一个大的问题. 在较老的语言中, 例如C++语言中, 内存管理是显式的, 也就是说使用者自己申请内存使用, 自己释放内存. 这就是为什么C++语言中除了构造函数, 还有析构函数. 我们在创建对象的时候调用构造函数创建, 系统会在对象结束其作用域的时候调用析构函数, 我们需要做的就是在析构函数中释放掉我们申请的相关资源, 以便释放内存地址.

    显然, 这种显式的由编程人员自己控制释放内存的方式很容易出问题, 忘了, 漏了, 都可能导致内存问题. 也不符合程序员要懒的特征.

    故而, Java语言中引入了自动内存管理的机制, 也就是垃圾回收器. 大部分的现代面向对象语言, 也都是采用自动内存管理机制.

    内存自动管理回收机制可以解决大部分, 但不是所有的内存问题, 这也是为什么我们要讨论内存泄露.

    垃圾回收器的职责

    垃圾回收器有三大职责:

    1. 分配内存;
    2. 确保任何被引用的对象保留在内存中;
    3. 回收不能通过引用关系找到的对象的内存.

    垃圾回收的一般流程

    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

    参考


    转载请注明出处, 欢迎大家分享到朋友圈, 微博~

    相关文章

      网友评论

      • FM小宇宙:学到了👍
      • andyhaha007:请教一下GC的Eden、Survivor和Old Generation为什么通常是1:1:8的比例 这个问题一直没找到好的答案
      • 1d346cdd4a27:讲的比较清晰,lz继续完善呀
      • dasan沈扬:可达的对象会移到Survivor区,如果Survivor区放不下是怎么处理的
        醉卧赏血:正如1楼所说,在eden区既然就会判定创建对象占用内存大小是否超过eden大小,超过则直接进入老年区,那么在进入survivor区的时候自然也要判定,同样超过则进入老年区。。。
      • w4lle:新生代 复制算法:将现有的内存空间分为两快(s0、s1),每次只使用其中一块,在垃圾回收时将正在使用的内存中的存活对象复制到未被使用的内存块中,之后,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收。
        老年代 标记-压缩算法 (Mark-Compact):先需要从根节点开始对所有可达对象做一次标记,但之后,它并不简单地清理未标记的对象,而是将所有的存活对象压缩到内存的一端。之后,清理边界外所有的空间。这种方法既避免了碎片的产生,又不需要两块相同的内存空间,因此,其性价比比较高。
        新生代对象会很快被回收,使用复制算法,空间换时间。
        老年代对象存活更久,使用标记压缩算法,时间换空间。
        摘自《Android GC 那点事》
      • One_Month:简单易懂,比深入java虚拟机好理解点
      • zthh:文中所有new对象都放在新生代有误。当new出来的对象的空间大于虚拟机中-XX:PretenureSizeThreshold参数时(即所谓的大对象)会直接放入老年代,而不会放入新生代。from《深入理解Java虚拟机》93页
        anly_jun:@SaladJack 谢谢指正 :+1:
      • liu灰1314:可以的
      • ostea:stp
      • coco猫:配图笑了。。。。
        anly_jun:@coco猫 第一眼看到也乐了 😊

      本文标题:GC那些事儿--Android内存优化第一弹

      本文链接:https://www.haomeiwen.com/subject/sykgyttx.html