如何判断对象已经死亡

一、引用计数器算法

给对象添加一个引用计数器,每当有一个地方引用它就使计数器+1,引用消失就-1。这样当这个对象的引用计数器为0的时候就代表没有地方对它进行引用,就可以对这个对象进行回收。

这种算法实现简单,高效,但是缺点是没法解决对象之间的相互引用,即A引用B,B引用A,初次之外他们俩没有在别的地方被引用,但是也不会被回收。

二、可达性分析算法

GC Roots出发向下进行搜扫,如果能够通过引用链到达一个对象,那么表明这个对象是需要被引用的,不会被回收,否则表示需要回收。

GC Roots
  • 虚拟机栈中的引用
  • 方法区中的静态属性引用
  • 方法区中的常量引用
  • 本地方法栈中的对象引用

4种引用类型

java1.2之前,如果Reference类型的数据中存储的数据代表着一块内存的起始地址,那么就表明这块内存代表着一个引用,这种定义太过狭隘和粗糙。所以在1.2之后对引用概念进行了扩充分入如下几种。

  • 强引用,即最普通的一种引用方式,类似于 “Object a = new Object();”
  • 软引用,SoftReference,在内存溢出之前对这种类型的引用进行回收,如果内存还是不够则抛出OOM。
  • 弱引用,WeakReference,对于一个只有弱引用的对象,它的生命周期只有一个GC时间,也就是说下一个GC到来的时候会无论内存是否充足都会对它进行回收。
  • 虚引用,PhantomReference,最弱的一种引用描述,这种引用不会相当于没有引用,唯一的作用就是对于引用的对象如果被回收是能够得到一个通知。

三、finalize方法

​ 即使经过可达性分析判定对象为已死亡的对象也不是立即进行内存回收的,它会判断这个对象有没有必要执行finalize方法,条件是必须覆盖了finalize,并且之前没有执行过。如果满足,则会放到一个叫做F-QUEUE的队列中去待执行。但虚拟机并不保证这个方法一定被执行完毕,只保证触发这个方法。我们可以利用这个机制让应该死亡的对象重新得到引用。但一般情况下都建议忽视这个方法,书中说这个方法是一个历史遗留,为了妥协c/c++程序员的,不要利用它做任何事情,包括资源释放。

垃圾回收算法

一、标记-清除算法

标记清除算法是一种最基础的算法,分为两个步骤,标记和清除。但是这种算法有两个缺点,第一个是效率不高,不论是标记还是清除。另一个是清除过后的内存区域会产生大量的碎片。

为什么标记-清除算法效率不高?

  • 每当堆中存储空间不够的时候会触发一个GC,那么在执行标记-清除之前会产生一个stop-the-world动作,暂停当前进程的执行。然后遍历GC Roots标记还在被引用的对象。然后对没有被引用的对象进行清除。
  • stop-the-world的原因是防止并发

二、复制算法

将可用的内存区域划分为两个部分,一次只用其中一个。当一块用的完的时候,就将还活着的对象复制到另一块区域,并将原来的区域清除掉,后面的对象在新的区域上分配,这样就避免了内存碎片的问题。但是这种做法的缺点很明显,就是一次只能使用一半的内存。

由于绝大部分实例的生命周期都非常短,一次GC只有少数的对象能活下来。所以在新生代,将内存分为一个Eden和两个Surivor。对象在Eden创建,一次GC把Eden中和一个Surivor中存活的对象移动到另一个Surivor中。并清除原来的空间。

但是你没有办法保证Surivor的内存足够存放活下来的对象,所以当这里的内存空间不够时会依赖其他内存进行担保。

三、标记-整理算法

复制算法的缺点是如果大部分对象的存活时间都非常长,那么就会进行非常频繁的复制。而且还需要额外的一半空间进行复制。所以在老年代使用 标记-整理算法,思想和标记-清除类似,但是后续步骤不是直接回收对象,而是让所有存活的对象都想一段移动,清理掉边界以外的内容。

四、分代收集算法

根据对象存活的周期特点,将内存分为新生代和老年代,各个年代的对象都有不同的特点,所以针对不同代有不同的算法。新生代使用复制算法,老年代是用标记-整理或者标记-清除。

内存分配策略

一、对象优先分配在Eden

一般情况下,对象有限在新生代的Eden区域分配,当内存不足时会触发一次Minor GC。

Minor GC和Full GC的区别

  • Minor GC发生在新生代,速度快,且频繁
  • Full GC 发生在老年代,速度慢,频率低,且一般一次Full GC会伴随着一次Minor GC

二、大对象直接进入老年代

大对象直接进入老年代的目的是避免了在新生代中来回的对大对象进行复制移动的步骤。

三、长期存活的对象进入老年代

虚拟机给每个对象定义了一个对象年龄计数器,每经过一次 Minor GC对象的年龄就会+1,当达到默认的15时,那么就会从新生代移动到老年代。

四、动态对象年龄判定

如果Surivor空间中一半的对象年龄都相同,那么年龄大于或等于该年龄的对象直接进入老年代。

五、空间分配担保

在Minor GC之前,先检查老年代最大可用空间是否大于新生代所有对象总空间,如果成立,则此次Minor GC是安全的(就算全部的新生代对象移动到老年代,也有地方放的下)。否则查看HandlePromotionFailure是否设置为允许担保失败,如果允许就会检查历次的历次的新生代晋升老年代的平均空间大小是否小于老年代可用空间,如果是,那就可以乐观的认为此次Minor GC可以进行。如果小于或者设置为不允许担保失败,那么会进行Full GC。

Q.E.D.