1. 对象存活分析
1.1 引用计数法
原理:给对象添加一个引用计数器,每当有一个引用时,加1,当引用失效时,减1,即引用技术为0时代表对象不再会被使用
缺点:解决不了相互引用的问题
1.2 可达性分析法
原理:GC Roots为起点,向下搜索,能链接到某个对象时,即此对象时可用的
优点:解决了引用计数出现循环引用的问题
可作为GC Roots的对象包含:
虚拟机栈中引用的对象
方法区中类静态属性引用的对象
方法区中常量引用的对象
本地方法栈中JNI(即native方法)引用的对象
1.3 引用
强引用 Strong Reference
只要引用还在(类似 Object obj = new Object()),垃圾收集器就永远都不会回收被引用的对象
软引用 SoftReference
还有用但不是必需的,软引用关联的对象,在系统将要发生内存溢出时,将此类对象列进回收范围进行二次回收,若内存还是不够则抛出内存溢出异常
弱引用 WeakReference
描述非必需对象,弱引用关联的对象只能活到下一次GC之前
虚引用 PhantomReference
虚引用也成为幽灵引用或者幻影引用,是最弱的一种引用关系,一个对象是否有虚引用的存在完全不会影响其存活时间,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用的唯一目的就是当对象被回收时收到一个系统通知。
1.4 没有任何引用的对象有一次自救机会
在可达性分析中不可达的对象,并非是一定要被回收的,至少需要经历两次标记过程:如果对象在进行可达性分后发现没有与 GC Roots 相连接的引用链,那么它将会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法,当没有覆盖finalize()方法,或者finalize() 已经被虚拟机调用过,虚拟机将这两种情况都视为“没有必要执行”,对象将会被回收。
若是有必要执行,那么对象将会被放入F-Queue 的队列中,并在稍后有一个虚拟机自动建立的,低优先级的Finalizer线程区执行它。稍后GC将对F-Queue中的对象进行二次标记,若在finalize()方法中成功自救(即与引用链上的任意对象关联上),将其移出F-Queue,否则基本上会被真的回收了。
1.5 回收方法区
主要收集废弃常量和无用的类
废弃常量:与回收Java堆中的对象非常相似。以常量池中字面量的回收为例,若一个字符串进入池中,但是没有任何一个对象引用此常量,如果此时发生内存回收,且有必要的话,此常量将会被回收。
无用的类,同时满足一下三点:
该类所有的实例都已经被回收,即Java堆中不存在该类的任何实例
加载该类的ClassLoader已经被回收
该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法
2. 垃圾收集算法
2.1 标记-清除算法
原理:首先标记出所有需要回收的对象,再标记完成后统一回收所有被标记的对象
缺点:
- 效率低
- 会产生内存碎片,提前触发GC
2.2 复制算法
原理:将内存分成两块,每次存活的对象复制进另一块内存,然后再把已使用过的内存空间清理掉。
优点:实现简单,效率高
缺点:内存利用率低,存活率较高时效率很低。
使用方式:调整第一块内存与第二块内存比率,如 Eden:Survivor=8:1,存活率较低时使用较好,如新生代
2.3 标记-整理算法
原理:首先标记出所有需要回收的对象,将存货的对象都向一段移动,然后清理掉端边界外的所有对象。
使用:存活率较高的情况,如老年代
2.4 分代收集算法
原理:根据分代的特点选择所需算法。如新生代使用复制算法,老年代使用标记-整理算法。
3. GC算法实现(Hotspot)
3.1 枚举根节点
3.2 安全点
- 抢先式中断
- 主动式中断
3.3 安全区域
4. 垃圾收集器
4.1 Serial收集器
原理: 不仅仅是指使用一个CPU或者一条线程去执行,并且在进行收集时,会暂停所有线程,知道他收集结束,即(Stop The World)
优势: 简单而高效,是虚拟机运行在Client模式下的很好的选择
4.2 ParNew收集器
Serial收集器的多线程版本,能与CMS收集器配合工作
4.3 Parallel Scavenge收集器
目标是达到一个可控制的吞吐量,适合在后台运算而不需要太多交互的任务。
-XX:MaxGCPauseMillis 最大垃圾收集停顿时间
-XX:GCTimeRatio 吞吐量大小
4.4 Serial Old收集器
Serial的老年代版本,使用“标记-整理”算法
4.5 Parallel Old收集器
Parallel Scavenge的老年代版本,使用多线程和“标记-整理”算法
4.6 CMS(Concurrent Mark Sweep)收集器
目标: 获取最短回收停顿时间
优势:并发收集,低停顿
缺点:1. 占用大约25%CPU资源 2.会产生浮动垃圾 3.内存空间碎片
使用: 跟用户交互频繁的场景
- 初始标记
标记 GC Roots 直接关联到的对象 - 并发标记
进行 GC Roots过程 - 重新标记
修正并发标记阶段因用户继续运作而导致标记产生变动的那一部分对象的标记记录 - 并发清除
并发清理
4.7 G1收集器
并行与并发
分代收集
空间整合
可预测的停顿
5. 内存分配与回收策略
1.对象优先在Eden分配
大多数情况下,对象在新生代Eden中分配,当Eden区没有足够空间进行分配时,将进行一次MinorGC,若GC期间Survivor区间容量不够,则提前进入老年代。
2.大对象直接进入老年代
大对象是指需要连续内存空间的JAVA对象,最典型的就是很长的字符串和数组。这种对象太频繁会导致提前GC
-XX:PretenureSizeThreshold参数,令大于此值的对象直接进入老年代。这样做可避免Eden区以及两个Survivor区之间发生大量的复制。
3. 长期存活的对象进入老年代
既然采用分代思想来进行管理,那么内存回收时必需能识别那些处于新生代,哪些处于老年代,为此,虚拟机给每个对象引入一个对象年龄计数器。对象经过一次MinorGC仍然存活将进入Survivor区,此时年龄+1,在Survivor中熬过一次GC,年龄+1,当达到指定的年龄之后,将其晋升到老年代。
-XX:MaxTenuringThreshold
4. 动态对象年龄判断
如果在Survivor中相同年龄的对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象将直接进入老年代,无需年龄达到阈值。
5. 空间分配担保
在发生MinorGC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总和,那么 Minor GC 可确保是安全的。否则
1.JDK6u24之前虚拟机会查看HandlePromotionFailure的值,允许担保失败,那么会继续检查老年代最大可用的连续空间是否大于历次晋升老年代的平均大小,若大于,则尝试进行一次 Minor GC,若担保失败,将进行一个 Full GC ; 若HandlePromotionFailure不允许担保失败,或者历次平均值小于现有新生代总和,则不允许冒险,将进行一次 Full GC.
2.JDK6u24之后HandlePromotionFailure值不在有影响,只会依据老年代连续空间大小大于新生代总和历次晋升平均大小,就进行Minor GC , 否则进行 Full GC。