1. 内存分配和回收策略
- 对象优先在Eden分配,当新生代区没有足够的内存时,通过分配担保机制提前转移到老年代中去。
- 大对象直接进入老年代。大对象是指需要大量连续内存空间的对象,虚拟机提供了参数
-XX:PretenureSizeThreshold
(只对Serial,PreNew连个回收期起效),令大于这个值的对象直接在老年代分配,避免了Eden和两个Survival之间发生大量的内存复制。 - 长期存活的对象将进入老年代。虚拟机给每个对象定义了对象年龄计数器(Age),如果对象在Eden出生,经过第一次Minor GC后依然存活,并且能被Survival容纳的话,将被移动到Survival,对象年龄设为1.对象在Survival中每熬过一次Major GC,年龄增加1,达到一定程度(默认是15),就会被晋升到老年代。对象晋升老年代的阈值,可以通过
-XX:MaxTenuringThreShold
指定。 - 动态对象年龄判断。如果在Survival空间中相同年龄所有对象的大小综合超过了Survival空间的一半,年龄大于等于这个年龄的对象都会被晋升到老年代。无需等待年龄超过
-XX:MaxTenuringThreShold
指定的年龄。 - 空间分配担保。只要老年代的连续空间大于新生代对象总和或者历次晋升的平均大小,就进行Major GC,否则进行Full GC。
2. 对象判活过程
2.1. 引用计数法
给对象调价一个引用计数器,每当有一个地方引用它时,计数器+1,引用失效时,计数器-1。优点:判定简单,效率高。缺点:无法解决相互虚幻引用问题
2.2. 可达性分析方法
通过一系列的成为“GC Roots”的对象作为起始点,从这些节点开始乡下搜索,搜索所走过的路径成为“引用链”,当一个对象到GC Roots没有任何引用链相连时,说明这个对象时可回收的。
Java语言中,可作为GC Roots的对象包括以下几种:虚拟机栈中引用的对象,方法区中类静态属性引用的对象,方法区中常量引用的对象,本地方法栈中JNI引用的对象。
引用(JDK 1.2后) 当内存空间还足够时,则能留在内存之中,如果内存空间在进行垃圾收集后还是非常紧张,则可以抛弃这些对象。
强引用
(Strong Reference)
类似Object a = new Object() ,只要强引用存在,垃圾回收器永远不会回收被引用的对象;
软引用(Soft Reference)
用来描述一些还有但并非必须的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列入回收范围之中进行二次回收。如果这次回收还没有足够的内存,跑出内存溢出;
弱引用(Weak Reference)
用来描述非必须对象,被弱引用关联的对象只能生存至下一次垃圾回收之前,当垃圾回收器工作时,无论内存是否足够都会被回收;
虚引用(Phantom Reference)
也称幽灵引用或者幻影引用。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例;为一个对象设置虚引用关联唯一的目的就是能在这个对象被收集器回收时收到一个系统通知。
2.3. 回收过程
- 当对象进行可达性分析没有与 GC Roots 相连的引用链,将会被第一次标记,并根据是否需要执行finalize()方法进行一次筛选,对象没有重写 finalize() 或者虚拟机已经调用 finalize(),都被视为不需要执行。
- 如果对象有必要执行 finalize,会被放入到 F-Queue 队列中,并在稍后由虚拟机自动创建的低优先级的 Finalize 线程去触发它,并不保证等待此方法执行结束。
- 如果对象在 finalize() 方法执行中,重新和 GC Roots 产生了引用链,则可以逃脱此次被回收的命运,但 finalize() 方法只能运行一次,所以不能通过此方法逃脱下一次被回收。
2.4. 回收方法区
主要包括废弃常量和无用类的回收。判断类无用:类的实例都被回收,类的ClassLoader被回收,类的Java.Lang.Class对象没有在任何地方引用。满足这三个条件,类才可以被回收(卸载)
HotSpot虚拟机通过 -Xnoclassgc参数进行控制是否启用类卸载功能。在大量使用反射,动态代理、CGLib等框架,需要虚拟机具备卸载功能,避免方法区发生内存溢出。
3. 垃圾回收算法
3.1. 标记-清除
先标记处所有要回收的对象,在标记完成后统一进行对象的回收。缺点:
- 标记和清除的效率不高。
- 空降问题,产生大量不连续的内存碎片,碎片太多都导致对象无法找到足够的内存,从提前触发垃圾回收。
3.2. 复制算法
新生代分为一个Eden,两个Survival空间,默认比例 8:1:1
。回收时,将Eden和一个Survival的存活对象全部放入到另一个Survival空间中,最后清理掉刚刚的Eden和Survival空间。
Survival空间不够时,由老年代进行内存分配担保。
3.3. 标记-整理
根据老年代对象的特点,先标记存活对象,将存活对象移动到一端,然后直接清理掉端边界以外的对象。
3.4. 分代收集
新生代采用复制算法,老年代采用标记-删除,或者标记-整理算法。
4. HotSpot算法实现
枚举根节点实现
可达性分析时进行GC停顿,停顿所有的Java线程。
HotSpot进行的是准确式GC,当系统停顿下来后,虚拟机有办法得知那些地方存在着对象引用,HotSpot中使用一组成为OopMap的数据结构来达到这个目的。
安全点
HotSpot没有为每个指令都生成OopMap,只在特定的位置记录这些信息,这些位置成为安全点。各个程序执行的时候轮询这个标志,发现中断标志为真时自己就中断挂起。
安全区域
解决没有分配Cpu时间的暂时不执行的程序停顿。
5. 对象死亡过程
即使是可达性分析算法中对象不可达时也是至少经历两遍标记的过程; 判断对象没有与GC Roots相连接的引用链,进行第一次标记并且进行第一次筛选,筛选条件为:此对象是否有必要执行finalize()方法。
- 重写
finalize()
方法。 finalize()
方法已经被虚拟机调用过,虚拟机将这两种情况都视为“没必要执行”。