垃圾收集器与内存分配
一.判断对象是否存活的算法:
引用计数法:
给对象添加一个引用计数器,每一个地方引用它时计数器加1;引用失效时计数器减1.为值为0的对象不可能再被使用.
缺点:
难以解决对象之间循环引用的问题(对象A引用对象B,对象B引用对象A,两个对象无实际意义)可达性分析算法:
通过一些称为”GC_ROOTS”的对象作为起点,开始向下搜索引用它的对象,这个过程走过的路径即”引用链”.当一个对象到”GC_ROOTS”没有任何引用链相连,证明此对象不存活.
可作为GC_ROORS的对象:
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
- 方法区中静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI(Native方法)引用的对象
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
二.四种引用方式:
强引用(Strong Reference):
程序代码当中普遍存在的类似”Object obj = new Object()”这类引用.
只要强引用存在,该对象永远不会被垃圾收集器回收.软引用(Soft Reference):
SoftReference类实现,用于描述还有用但非必需的对象.
在系统将要发生内存溢出异常之前,会对这些软引用对象进行二次回收.弱引用(Weak Reference):
WeakReference类实现,也是用于描述非必须对象,强度比软引用更弱.
无论内存是否足够,弱引用对象都会在下一次垃圾收集发生时进行回收.虚引用(Phantom Reference):
PhantomReference类实现,虚引用无法影响对象的生存时间,唯一用处是在对象被回收时收到一个系统通知.
三.对象死亡的判定过程:
一个对象真正被宣告死亡,至少经过两次标记过程:
第一次标记:对象经过可达性分析判定不存活
判断对象是否覆盖或者是否被调用过finalize方法.若未覆盖或已被调用则直接回收对象,否则将对象放入
F-Queue
队列中去触发finalize方法第二次标记: GC对
F-Queue
中的对象进行筛选,检查对象是否在finalize()中与引用链上的对象进行关联(自救),是则被移出回收集合,否则标记
四.垃圾收集的算法:
标记 - 清除算法:
标记阶段:标记出所有需要回收的对象
清除阶段:统一回收所有被标记的对象
- 缺点:
- 效率:标记和清除两个过程效率都不高.
- 空间:标记清除过后会产生大量不连续的内存碎片,导致不发分配足够的连续内存给大对象而提前出发一次垃圾收集
- 缺点:
复制算法(用于新生代):
将可用内存按容量分为大小相等的两块,每次只使用其中一块.当一块内存用完后,将存活对象复制到另一块上,然后一次性清理使用过的空间.
优点:
实现简单,运行高效缺点:
内存缩小为一半,代价太高
新生代的复制算法:
将新生代内存分为一个较大的Eden和两个较小的Survivor空间,每次使用Eden和一个Survivor.
当回收时将Eden和Survivor中存活的对象一次性复制到另一块Survivor中,清理刚用过的Eden和Survivor空间.
当Survivor中不足以存放上一次存活对象时,这些对象将直接通过分配担保机制
进入老年代.
标记 - 整理算法(用于老年代):
标记阶段:标记出所有需要回收的对象
整理阶段:让所有存活的对象都向一端移动,然后直接清理端边界以外的内存
缺点:
整理过程耗时分代收集算法:
把Java堆分为新生代和老年代,根据各个年代特点采用合适的收集算法.
新生代:每次GC都有大批对象死去,只有少量存活,故使用复制算法(实际上经过改进)
老年代:对象成活率高,使用标记-整理算法
五.内存的分配与回收策略(对象年龄判定):
新生代: Eden + Survivor*2
Eden:Survivor = 8:1
对象优先在Eden上分配:
每次分配可用空间为Eden+Survivor,另一个Survivor在下次垃圾收集时用于存放复制的存活对象.
长期存活的对象进入老年代:
对象在Survivor区中每存活一次Minor GC,年龄就增加一岁,当年龄增加到一定程度(默认为15岁)就会从新生代晋升到老年代中
对象年龄的动态判定:
如果在Survivor空间中相同年龄所有对象所占空间大于Survivor空间的一半,则大于等于此年龄的对象可以直接晋升老年代.
空间分配担保:
老年代剩余空间 > 之前转入老年代的对象的平均大小 —> 直接Major GC
老年代剩余空间 < 之前转入老年代的对象的平均大小 && 允许担保失败 —> 直接Minor GC,不需要做Full GC
老年代剩余空间 < 之前转入老年代的对象的平均大小 && 不允许担保失败 —> 触发Full GC