如何判断对象是否不再被引用
引用计数法(Referentce Counting)
在对象中添加一个引用计数器,每当有一个地方引用,计数器值就加1,当一个引用失效时,计数器值就减1。该方法需要额外的存储空间,虽然算法简单,实现简单,但是会引起一些问题(比如无法解决对象之间循环引用的问题),主流的Java虚拟机都没有采用这种方式。
可达性分析法(Reachability Analysis)
通过一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索构成所走过的路径称为“引用链(Reference Chain)”,如果某个对象到GC Roots之间没有任何引用链连接,那么该对象就不可能再被使用。
可作为GC Roots的对象包括:
- 虚拟机栈中引用的对象
- 方法区中类 静态属性 所引用的对象
- 方法区中常量所引用的对象
- 本地方法栈中JNI所引用的对象
- Java虚拟机内部的引用
- 所有被同步锁(synchronized关键字)持有的对象
- 反应Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。
引用类型:
-
强引用:
最“传统”的引用过定义,指在程序代码中普遍存在的 引用赋值(即类似 Object obj = new Object()
)。只要强引用关系还在,垃圾收集器就永远不会回收掉被引用的对象。
-
软引用:
用来描述一些还有用,但非必须的对象。 如果一个对象只具有软引用,且内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。
-
弱引用
弱引用与软引用的区别是,如果对象只被软引用所引用,当GC时不管内存是否充足都会被回收。
-
虚引用
虚引用是最弱的引用,它不会对对象的生存时间构成影响,也无法通过虚引用来取得一个对象实例。虚引用对象在任何时候都可以被回收。
如何处理不再被引用的对象
在经过可达性分析后,那些不再被引用的对象不会立刻被回收,只有经过了两次标记之后,才会被回收。
垃圾收集算法
分代收集理论
分代收集理论建立在三个分代假说之上:
- 弱分代假说:绝大多数对象都是朝生夕灭的
- 强分代假说:熬过越多次垃圾收集过程的对象就越难以消亡
- 跨带引用相对于同代引用来说只占极少数
前两个假说奠定了多款垃圾收集器的统一设计原则:收集器应该将Java堆划分出不同的区域,然后将回收对象依据其年龄(经历垃圾收集过程的次数)分配到不同的区域之中存储。
把堆分区之后,垃圾收集器才能每次只回收其中一个或一些区域
回收类型划分:
- Partial GC:指目标不是收集整个Java堆的垃圾收集,其中又包括:
- Minor GC/Young GC:目标只是 新生代 的垃圾收集
- Major GC/Old GC:目标只是 老年代 的垃圾收集
- Mixed GC:收集整个新生代和部分老年代
- Full GC:收集整个Java堆和方法区
标记-清除(Mark-Sweep)算法
先标记出所有需要收集的对象,标记完成后,统一回收所有标记了的对象。也可以反过来标记存活的对象,收集所有未被标记的对象。
缺点:
- 效率不稳定:如果存在大量需要比清理的对象,就需要做大量的标记与清理工作,导致标记和清理工作的效率随堆中对象数量的增加而降低;
- 可能导致大量碎片:大量不连续的碎步片导致无法找到足够大的连续内存,从而不得不提前触发另一次GC。
标记-复制算法
简称为复制算法,为了解决标记-清除算法面临大量可回收对象时,效率低下的问题。
半区复制算法(Semispace Copying):将可用内存按容量 平分 为两块,每次只是用其中一块,当这一块内存用完时,就将还存活着的对象复制到另一块上,然后再把这块空间全部清理掉。
优点:不会产生内存碎片,因为是顺序分配内存,且统一回收的。
缺点:将可用内存缩小为了原来的一半;若一次复制存在大量存活的对象,那么就会产生大量的内存间复制开销。
目前大量商用虚拟机都采用该算法对 新生代 进行垃圾收集。
更加优化的半区复制算法
IBM公司对新生代“朝生夕灭”的特点做了更量化的诠释:新生代中的对象,有98%熬不过第一轮收集。所以我们不必按1:1的比例来划分新生代的内存空间。
Appel式回收:
将新生代内存空间划分为一个较大的Eden空间和两个较小的Survivor空间。Appel式回收将对象存储在Eden区或者其中一块Servivor区上,另一块Servivor区空闲。当发生垃圾收集时,将存活的对象一次性复制到空闲的Servivor区,然后清理Eden区和已用的Survior区。HotSpot虚拟机默认的Eden和Survivor的到小比例是8:1。如果一次Minor GC,Survivor空间不足以容纳存活的对象,那么部分存活的对象将直接进入老年代。
标记-整理(Mark-Compact)算法
该算法用户收集 老年代 对象。其标记过程与标记-清除算法一样,但在标记之后,不是直接清除,而是将存活的对象向内存的一端移动,然后再清理掉边界以外的内存区域。
内存分配原则
大对象指那些需要连续的大量空间的Java对象,比如很长的字符串,或很长的数组等。
虚拟机为每个对象定义了一个对象年龄计数器,存储在对象头中。对象通常在Eden区里诞生,如果经历一次Minor GC后任然存活,并且能被Survivor容纳的话,该对象就会被移入Survivor区,并且将其对象年龄设置为1,以后每经历过一次Minor GC并存活下来,年龄加1,当年龄达到一定程度(默认为15),该对象就晋升到老年代中去。
动态对象年龄:HotSpot虚拟机并不是一定要求对象的年龄达到阈值才将其存入老年代。当Survivor区里 相同年龄 对象占用的空间大于Survivor区容量的一半,年龄大于等于该年龄的对象就可以直接进入老年代。