判断对象“存活” 或 “死亡”
(一)引用计数器法
思路:为对象添加一个引用计数器,对象被引用一次,计数器加1,;当引用失效时,计数器减1;当对象的计数器为0时,则判断对象不可用;
存在问题:引用计数器无法解决对象间的相互循环引用,因此主流的java虚拟机未使用该方式管理内存。、
示例:
public class ReferenceCountingGc {
public Object instanceObject = null;
private static final int capacity =1024*1024;
/**
* 定义一个成员变量,占用内存,在垃圾回收时从日志体现垃圾回收过程
*/
private byte[] bigSize = new byte [2*capacity];
public static void main(String[] args) {
ReferenceCountingGc obj1 = new ReferenceCountingGc();
ReferenceCountingGc obj2 = new ReferenceCountingGc();
obj1.instanceObject = obj2;
obj2.instanceObject = obj2;
obj2=null;
obj1=null;
System.out.println("----------------------------------");
//手动进行垃圾回收,通过日志查看两个对象回收
System.gc();
}
}
GC日志:
[GC[DefNew: 3133K->481K(4928K), 0.0036787 secs] 3133K->2529K(15872K), 0.0037363 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
----------------------------------
[Full GC[Tenured: 2048K->480K(10944K), 0.0031065 secs] 4679K->480K(15872K), [Perm : 183K->183K(12288K)], 0.0031472 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heap
def new generation total 4992K, used 44K [0x25200000, 0x25760000, 0x2a750000)
eden space 4480K, 1% used [0x25200000, 0x2520b398, 0x25660000)
from space 512K, 0% used [0x25660000, 0x25660000, 0x256e0000)
to space 512K, 0% used [0x256e0000, 0x256e0000, 0x25760000)
tenured generation total 10944K, used 480K [0x2a750000, 0x2b200000, 0x35200000)
the space 10944K, 4% used [0x2a750000, 0x2a7c8208, 0x2a7c8400, 0x2b200000)
compacting perm gen total 12288K, used 183K [0x35200000, 0x35e00000, 0x39200000)
the space 12288K, 1% used [0x35200000, 0x3522dd70, 0x3522de00, 0x35e00000)
ro space 10240K, 44% used [0x39200000, 0x3967b1c8, 0x3967b200, 0x39c00000)
rw space 12288K, 52% used [0x39c00000, 0x3a2431d0, 0x3a243200, 0x3a800000)
- 4679K->480K : System.gc()执行后,内存完成回收;没有因为两个对象互相引用而无法造成内存无法回收,也就说明虚拟没用使用引用计数器法来判断对象存活。
(2)可达性分析法
从一个GC ROOTs 对象节点开始,依次往下搜索,搜索所走过的路径称为引用链,,当一个对象与GC ROOTS节点没有引用链,则认为该对象“死亡”,则表明该对象可以回收。
示例
实际上,对象回收并不是没有引用链就立即被回收,被回收要经历两次标记才能最终被回收
引用:
JDK1.2之对引用定义:如Reference类型存储的值是指向另一块内存的起始地址,就称这块内存是一个引用。
JDK1.2后,将引用的概念进行扩充;将引用分为强引用、软引用、弱引用、引用
- 强引用: new 出来的对象,强引用不会被垃圾收集器回收
- 软引用: 一些有用但是非必须的对象;在内存即将溢出时将这部分对象进行回收
- 弱引用: 被弱引用关联的对象只能活到下一次垃圾回收期之前,无论内存是否会发生溢出,都会回收;
- 虚引用:“幽灵”引用,对象是否有虚引用不影响其生存时间
四种垃圾收集算法
标记-清除算法
顾名思义,算法需要经过“标记”、“清除”两步,先标记所有要回收的对象,再一次性清除标记的对象来回收内存
清理前:
清除后:
缺点:
(1)标记、清除都要扫描整个内存区,标记清除效率低;
(2)产生大量内存碎片,当大对象需要分配连续内存时,没有连续内存会导致再次出发垃圾收集动作;
复制算法
复制算法:将内存分为相同大小的两块,每次只使用其中一块,这块使用完后,将存活对象复制到另一块上,再将使用过的内存一次性清理
清理前:
复制清理后:
缺点:
(1)将内存缩小了一半
实际内存并非按照1:1来划分的,而是将内存划分为Eden区和两块小的survivor,HosPsot虚拟机默认比例Eden:survivor:survivir = 8:1:1;每次收集,将eden和Survivor区域存活对象复制到另外一块儿Survivor,再一次清理用过的的Eden和Survivor。
标记-整理算法
算法:该算法分为“标记”和“整理”两部分,标记和“标记清除-清除算法”标记过程一致,标记后不直接清除,而是将存活对象整理到内存另一端,然后清理以外的内存。
清理后:
分代收集算法
根据对象存活周期同将内存划分为几块,一般将堆内存分为新生代和老年代,根据不存代对象的特点进行对象清理。
(1)新生代:对象“朝生夕死”,垃圾收集时大量对象死亡,只存活少量对象,使用复制算法,只需要付出少量对象复制成本即可完成收集
(2)老年代:存活对象不会轻易被垃圾收集,且可能是大对象,没有额外空间进行分配担保,则必须采用“标记-整理”算法或“标记-清理”算法进行垃圾回收。
创建对象优进入新生代的Eden区域
大对象:需要连续占用内存空间的对象,如长字符串,大的数组
老年代:
- 大对象直接进入老年代
- 长期存活的对象进入老年代(虚拟机为每个对象设置年龄计数器,经过一次MinorGC并能成功进入>surivor内存空间,则年龄计数器+1,可以通过参数设置最大年龄)
空间分配担保:新生代使用复制算法,当Minor GC时,任然存活大量对象,另外一块儿Survivor无法容纳,无法容纳的对象直接进入老年代,因此老年代需要提供容纳这些对象的空间
- Minor GC : 发生在新生代的垃圾收集动作,由于新生代对象特点,Minor GC较为频繁
- Full Gc /Major Gc :发生在老年代的垃圾收集动作,进行一个Major GC 伴随一次Minor GC (不是绝对),Major GC 的速度比Minor满