对象回收
堆分配情况
虚拟机把堆内存划分为三个区域:新生代(Young Generation),老年代(Old Generation),持久代(Permanent Generation)
回收判断
引用计数法
给对象添加一个引用计数器,没当有一个地方引用它时,计数器值就加一,当引用失效时,计数器值就减一;任何时刻计数器为0的对象就不可能再被引用了。
缺点:
1. 当对象之间存在循环引用时,会导致它们的引用计数器值都不为0,无法被回收。
2. 引用计数要求在每次因引用产生和消除的时候,需要进行一个加法和减法的操作,可能会影响系统性能
可达性分析算法
基本思想就是通过一系列称为GC Roots的对象作为起点,从这些节点开始向下搜索,搜索走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连的话,则证明此对象是不可用的。
GC Roots包括以下几种:
* 虚拟机栈(栈帧中本地变量表)中引用的对象
* 方法区中类静态属性引用的对象
* 方法区中常量引用的对象
* 本地方法栈中JNI(Native方法)引用的对象
方法区的回收
Hotspot中的永久代,永久代中的回收主要有两部分:废弃常量和无用的类,
废弃常量:
当没有任何其他地方引用了这个字面量就可以回收了
无用的类:
1. 该类的所有实例都已经被回收了,也就是Java中不存在该类的任何实例
2. 加载该类的ClassLoader已经被回收了
3. 该类对应的Java.lang.Class对象没有在任何地方呗引用,无法再任何地方通过反射访问该类的方法。
虚拟机可以对满足上诉三个条件的类进行回收,但并不是和对象一样,不使用了就回收。
在大量使用反射,动态代理,CGLib等ByteCode框架,动态生成JSP以及OSGi这类频繁自定义ClassLoader的场景都需要虚拟机具备类卸载的能力,以保证永久代不会溢出。
垃圾收集算法
标记-清除(Mark-Sweep)
两个阶段:①标记出所有需要回收的对象,②标记完成后,统一进行回收
缺点:
1. 效率问题:标记和清除的效率都不高
2. 标记清除将会产生大量不连续的内存碎片,空间碎片太多将会导致分配大对象的时候,没有足够的连续空间,不得不提前触发垃圾回收动作
复制(Copying)
将可用内存分为两块,每次只使用其中一块,当这一块使用完后,就将还活着的对象复制到另一块上去,之后,清除正在使用的这边的内存块中的对象,然后,交换两个内存的角色,不用考虑内存碎片等问题。
新生代串行回收器采用这种算法(因为垃圾对象通常多于存活对象)来回收,Eden 8 ,from 1 ,to 1。
标记-整理(Mark-Compact)
标记过程和标记-清除算法一样,后面不是对可回收对象直接进行清理,而是让存活的对象都移动到一端,然后将边界以外的直接清理掉。
标记-整理适合发生在老年代,大部分对象都是存活对象。如果用复制算法,复制成本较高。
分代算法(Generational Collecting)
复制,标记-清除和标记-整理,都没办法完全替代其他算法,他们有各自的优缺点。
分代算法,它将内存区间根据对象的特点分成几块,根据每块内存区间的特点,使用不同的回收算法。以提高垃圾回收的效率。新生代使用复制算法,老年代使用标记-清除或者标记-清理
新生代回收频率高,耗时段,老年代回收频率低,耗时长。为了适应新生代的回收,虚拟机使用一种叫卡表(Card Table)的数据结构。卡表是一个比特位集合。每一位可以表示4KB的老年代区域,是否持有新生代对象的引用,当标记位为0,表示不持有,为1的时候表示持有。当垃圾回收时,只需要扫描卡表标记位为1 的区域,可以大幅提高效率。
分区算法(Region)
分区算法将整个堆空间分为连续的不同的小区,每一个小区独立使用,独立回收,这种算法好处可以控制一次回收多少个小区域。