读书笔记《深入理解Java虚拟机》 (三)对象已死?与内存分配策略

对象是否可回收

  • 引用计数算法

给对象添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时就减1;当等于0时就认为对象不可能再被使用。问题:当两个对象相互引用时,就无法回收了。

  • 可达性分析算法

通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连,则证明对象是不可用的。

可作为GCRoots的对象包括下面几种:

  1. 虚拟机栈(栈帧中的本地变量表)中引用的对象。
  2. 方法区中类静态属性引用的对象。
  3. 方法区中常量引用的对象。
  4. 本地方法栈中JNI(Native方法)引用的对象。

引用的种类

  • 强引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象,类似“Object obj = new Object()”
  • 软引用,SoftReference,描述一些有用,但是非必需的对象,在系统将要发生内存溢出异常前会将此类引用列入回收范围进行第二次回收。
  • 弱引用,WeekReference,被弱引用关联的对象只能生存到下一次垃圾收集,无论当前内存是否足够。
  • 虚引用,PhantomReference,无法通过虚引用来取得一个对象实例。为一个对象设置虚引用的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。

对象的自我拯救

重写finalize()方法,只能执行一次。 

方法区的回收

垃圾收集率低, 常量池中的无用常量、无用类就算达到了回收条件也未必一定会被回收。

垃圾收集算法

  • 标记 — 清除算法

首先标记出所有需要回收的对象,在标记完成后统一回收。不足:效率低;会产生大量不连续的内存碎片。空间碎片太多可能会导致以后在程序运行过程中分配较大对象时,无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。

扫描二维码关注公众号,回复: 4442666 查看本文章
  • 复制算法

将内存块划分为容量大小相同的两块,每次只使用其中的一块,当这块内存用完时,就将这块内存中还存活的对象复制到另外一块内存中,并将当前内存块清理掉。实现简单,运行高效。但是内存缩小一半。

  • 标记 — 整理算法

首先标记出所有需要回收的对象, 然后把还存活的对象向一端移动,然后清理存活对象边界以外的空间。

  • 分代收集算法

根据对象存活周期的不同将内存划分为几块。 一般分为新生代和老年代。新生代每次垃圾回收都会有大批对象死去,所以选用复制算法,存活对象少, 复制成本低。老年代中对象存活率高,没有额外的空间对它进行分配担保,所以就使用“标记 —清理”或者“标记 — 整理”算法来进行回收。

内存分配与回收策略

  • 对象优先在Eden分配

大多数情况下,对象再新生代Eden区中分配。当Eden区中没有足够空间时,虚拟机会出发一次Minor GC(新生代GC)。

  • 大对象直接进入老年代

大对象是指需要大量连续内存空间的Java对象,比如很长的字符串以及数组。抵制String用加号多次拼接吧。大对象大多的话, 会增加提前触发垃圾收集的几率。

  • 长期存活的对象将进入老年代

虚拟机给每个对象定义了个年龄计数器,每经过一次新生代GC(Minor GC)还存活的话, 那年龄计数器就加1岁,直到增加到一定程度(默认为15岁),就会移动到老年代中。

  • 动态对象年龄判定

如果在Survivor空间中相同年龄所有对象的大小总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代。

空间分配担保

在发生新生代GC之前,虚拟机会先检查老年代最大可用空间是否大于新生代对象的总空间,如果不大于,则担保失败。如果允许担保失败,那么新生代GC有可能会失败,因为新生代GC需要复制出来的存活对象不能确定大小。当不允许担保失败或者新生代GC失败,会触发Full GC。一般是允许担保失败,来减少Full GC的次数。

猜你喜欢

转载自blog.csdn.net/baitianmingdebai/article/details/84894907