Java&Android 基础知识梳理(4) 垃圾收集器与内存分配策略

1、概述

GC须要考虑的三个问题:java

  • 哪些内存须要回收
  • 何时回收
  • 如何回收

在分析内存区域的时候,咱们把Java运行时数据区分为两个部分:算法

  • 程序计数器、虚拟机栈、本地方法栈:每一个栈帧中分配多少内存在类结构肯定下来就已知,所以这些区域的内存分配和回收具有肯定性,方法结束或线程结束时,内存就跟着被回收了。
  • Java堆、方法区:因为一个接口中的多个实现类须要的内存可能不同,一个方法中的多个分支须要的内存也不同,只有在程序处于运行期间才能知道会建立哪些对象,所以这些区域的内存分配和回收是动态的。

2、如何判断哪些是“存活”的实例

2.1 引用的分类

引用的定义:若是reference类型的数据中存储的数值表明的另一块内存的起始地址,就称这块内存表明引用。 引用的分类:数组

  • 强引用(Object a = new Object()):只要强引用存在,垃圾回收器永远不会回收掉被引用的对象。
  • 软引用(SoftReference):有用但并不是必须,在系统将要发生OOM异常以前,将会把这些对象列进回收范围中进行第二次回收。
  • 弱引用(WeakReference):非必须对象,被弱引用的对象只能生存到下一次垃圾收集发生前。
  • 虚引用(PhantomReference):不会对生存时间产生影响,也没法经过虚引用来取得一个对象实例,设置虚引用的惟一目的就是能在这个对象被垃圾回收器回收时收到一个系统通知。

2.2 引用计数法

给对象添加一个引用计数器,当有一个地方引用它时就加一,引用失效时就减一,当计数器的值为零时表示它不可用。 可是它没法解决相互循环引用问题安全

2.3 可达性分析

经过一系列的称为GC Roots的对象做为起始点,从这些节点开始向下搜索,所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链时,表示这个对象不可用,GC Roots的类型有:线程

  • 虚拟机栈中的局部变量表中引用的对象。
  • 方法区中类静态属性引用的对象。
  • 方法区中常量引用的对象。
  • 本地方法栈中**JNI**引用的对象。

2.4 finalize方法对于内存回收的影响

当某个对象在通过可达性分析后,发现它到GC Roots没有任何引用链时,那么它会被第一次标记,并进行第一次筛选,筛选的结果有两种状况:code

  • 没有覆盖finalize()方法或者虚拟机已经调用过它的finalize()方法:直接回收。
  • 其它状况:把这个对象放置在一个F-Queue的队列中,并在稍后由一个由虚拟机自动创建的、低优先级的Finalizer线程去执行这个对象的finalize()方法,如对象要在finalize方法中拯救本身,只要从新与引用链的某个变量关联便可,那么在第二次标记时它将被移出“即将回收”的集合,不然它将被回收。

这种方法代价高昂,不肯定性大,没法保证各个对象的调用顺序,所以能够忘记这个方法的存在。对象

3、方法区的回收

对于方法区(HotSpot中的永久代)主要回收两部份内容:废弃常量和无用的类。接口

  • 废弃常量 以常量池中字面量的回收为例,若是一个字符串abc被放入了常量池中,可是没有任何一个String对象引用它,那么就会被清理出常量池,常量池中其它类(接口)、方法、字段的符号引用也相似。
  • 类 同时知足三个条件:
  • 该类的全部实例已经被回收
  • 加载该类的ClassLoader已经被回收
  • 该类对应的java.lang.Class对象没有在任何地方被引用,没法在任何地方经过发射访问该类的方法。

4、垃圾收集算法基础

4.1 标记 - 清除算法

  • 概念 首先标记出全部须要回收的对象,在标记完成后统一进行回收。
  • 缺点:
  • 标记和清除两个过程效率不高。
  • 产生内存碎片,致使须要分配较大对象时,没法找到足够的连续内存而须要触发一次GC操做。

4.2 复制算法

  • 概念 将可用内存划分为大小相等的两块,每次只使用其中的一块,当一块内存用完了。则触发一次GC操做,将活着的对象复制到另外一块上,而后再把已使用的内存空间一次清理掉。队列

  • 缺点 将内存缩小为了原来的一半。内存

  • 如今商业虚拟机采用这种算法的改良版来实现新生代的回收 它把内存按8:1:1分为Eden/survivor0/survivor1三块: 须要分配内存时,首先尝试在Eden区分配,若是Eden区没法分配,那么尝试把活着的对象放到survivor0中去:

  • 若是survivor0能够放入,那么放入以后清除Eden区。

  • 若是survivor0不能够放入,那么尝试把Edensurvivor0的存活对象放到survivor1中:

    • 若是survivor1能够放入,那么放入survivor1以后清除Edensurvivor0,以后再把survivor1中的对象复制到survivor0中,保持survivor1一直为空。
    • 若是survivor1不能够放入,那么直接把它们放入到老年代中,并清除Edensurvivor0,这个过程也称为分配担保
  • 适用状况 因为复制算法在对象成活率较高时,须要较多的复制操做,效率会变低,因此在老年代中不能采用该算法。

4.3 标记 - 整理算法

  • 概念 和标记 - 清除算法相似,但后续步骤不是直接对可回收对象进行清理,而是让全部存活的对象都向一端移动,而后直接清除掉端边界之外的内存。
  • 优势 解决了标记- 清除算法致使的内存碎片问题在存活率较高时复制算法效率低的问题

4.4 分代收集算法

当前商业虚拟机采用的方式,根据对象存活周期的不一样将内存划分为几块,通常是新生代和老年代:

  • 新生代:每次垃圾收集时只有少许存活,选用复制算法的改良版,也就是上面说到的Eden/survivor0/survivor1的分配方式。
  • 老年代:对象存活率较高,且没有分配担保,必须用标记 - 清除或标记 - 整理算法来实现。

5、Minor GCMajor GC/Full GC

  • Minor GC:发生在新生代的垃圾回收动做,很是频繁,回收速度也较快,采用的垃圾收集器有SerialParNewParallel Scavenge
  • Major GC/Full GC:发生在老年代的GC,常常伴随至少一次的Minor GCMajor GC的速度通常会比Minor GC慢十倍以上,采用的垃圾收集器有CMSSerial OldParallel Old

6、对象分配的原则

  • 对象优先在Eden区分配 当Eden区没有足够空间,触发一次Minor GC

  • 大对象直接进入老年代 例如很长的字符串以及数组,常常出现大对象容易致使内存还有很多空间时就提早触发垃圾收集以获取足够的连续空间来“安置”它们。

  • 长期存活的对象将进入老年代 若是Eden区出生并进过第一次Minor GC后,仍然存活,而且被成功复制到survivor区中,那么对象年龄变为一,当对象在survivor中每熬过一次Minor GC,年龄就增长一,当年龄增长到必定程度,就会晋升到老年代中。

  • 动态对象年龄绑定 若是survivor空间中相同年龄全部对象大小的总和大于survivor空间的一半,年龄大于或等于该年龄的对象就能够进入老年代,无须到达要求的年龄。

  • 空间分配担保 在发生Minor GC前,检查老年代最大可用连续空间是否大于新生代全部对象总空间:

  • 大于,那么操做是安全的,不对老年代进行Full GC

  • 小于,检查HandlePromotionFailure设置值是否容许失败:

    • 容许:检查老年代最大可用连续空间是否大于历次晋升到老年代对象的平均大小:
      • 大于:不对老年代进行Full GC。在这以后,由于有可能出现某次存活对象激增的状况,这种属于冒险行为,若是出现了担保失败(也就是Edensurvivor0的存活对象既没法放入survivor1,也没法放入老年代的连续空间中),那么会在失败以后对老年代进行Full GC
      • 小于:先对老年代进行一次Full GC
    • 不容许:先对老年代执行一次Full GC
相关文章
相关标签/搜索