一.垃圾:即内存中已经没有用的对象
java 虚拟机决定内存是否回收的算法:可达性分析法
可达性分析法:将内存中一系列名字为GC Roots 的对象作为起点,由上至下搜索,所走的路径成为引用链,最后通过判断对象的引用链是否可达来作为对象是否回收的依据。
可以作为GC Roots 的对象:
- 虚拟机栈中局部变量表的中的引用对象
- 方法区中静态引用指向的对象
- 还存活的线程对象
- Native方法中的JNI引用的对象
不同的虚拟机有着不同的垃圾回收机制,但一般GC回收都会在以下两种情况下触发:
- Allocation Failure
- System.gc()
二. 垃圾收集算法
1. 标记-清除算法
两个阶段:
Mark阶段,从GC Roots 开始 遍历内存中的对象,可达的对象标记为存活对象,不可达的标记为垃圾对象;
Sweep 阶段,当遍历完所有的GC Roots 之后,将标记为垃圾的对象直接清除。
优点:实现方式简单,不需要对对象进行移动
缺点:执行过程中需要中断进程中的其他组件执行,可能产生内存碎片,提高了垃圾回收的频率
2.复制算法
将现有内存分为两块,每次只使用其中的一块,垃圾回收的时候将正在使用的内存中存活的对象复制到未被使用的内存中,然后清除正在使用的内存中的所有对象,交换两块内存的角色,完成垃圾回收。
优点:按顺序分配内存,简单高效,不用考虑内存碎片
缺点:内存的利用率比较低,因为内存只能使用一半
3.标记-压缩算法
从根节点开始对所有可达对象进行标记,之后将存活对象整理到内存的另一端,然后清理边界外的所有空间。
两个阶段:
Mark 阶段,
Compact 阶段
优点:这种方法既避免了碎片的产生,又不需要两块相同的内存空间,因此,其性价比比较高。
缺点:所谓压缩操作,仍需要进行局部对象移动,所以一定程度上还是降低了效率。
JVM 分代回收策略
JVM 虚拟机将堆内存分为新生代、老年代。 中心思想是 新创建的对象都分配到新生代中,如果经过多次回收仍旧存活的话,则将其移动到老年代中。
新生代 又被分为Eden 区 和两个Survivor区 Survivor From和Survivort To,默认比例是8:1:1,在最初时,新创建的对象先分配到Eden区中,当进行一次GC 时,首先将Eden 区中的垃圾清除,所有存活对象被移动到Survivor From 区中,再一次GC 时,Eden 区和Survivor From 区中的垃圾将被清出,然后所有存活对象被移动到Survivor To 区中,此时 Survivor From 区 同 Survivor To 区 调换,From 区成为To 区, To区成为From 区,不管怎样,都会保证名为To的Survivor区域是空的,Minor GC会一直重复这样的过程,如此切换多次后(默认15次)如果还有存活对象,会将存活对象移动到年老代中。
因为新生代涉及到对象的移动,进行复制操作,所以新生代一般采用复制算法。
老年代 中的对象生命周期比较长,一般不涉及复制操作,多采用标记压缩算法。
对于老年代可能存在这么一种情况,老年代中的对象有时候会引用到新生代对象。这时如果要执行新生代 GC,则可能需要查询整个老年代上可能存在引用新生代的情况,这显然是低效的。所以,老年代中维护了一个 512 byte 的 card table,所有老年代对象引用新生代对象的信息都记录在这里。每当新生代发生 GC 时,只需要检查这个 card table 即可,大大提高了性能。
引用:
软引用隐藏问题
需要注意的是,被软引用对象关联的对象会自动被垃圾回收器回收,但是软引用对象本身也是一个对象,这些创建的软引用并不会自动被垃圾回收器回收掉。