判断对象是否还活着
引用计数算法
- 每当有第一个地方引用了对象,计数器就+1 当失效就-1,如果计数器为0 那么对象就不可能再被使用了。
可达性分析法
- 通过一些列成为 gc roots的对象作为起始点,从这些结点开始搜索,搜索走过的路径称为引用链,当一个对象到 gc roots 没有任何引用链相连,则证明该对象是不可用的。
- 可用作为gc roots的对象包括
- java栈(局部变量表的)中的引用对象。
- 方法区的静态属性引用的对象
- 方法区常量引用的对象
- 本地方法栈中JNI 引用的对象
- 生存还是死亡?
- 即使被标记为不可达对象,也并不是直接清理了。
- 如果要宣告一个对象死亡,至少要经历两次标记过程。
- 第一次标记的时候会进行一次筛选,条件是此对象是否有必要执行finalize()方法,如果对象没有覆盖或者finalize()方法已经被虚拟机调用过了,虚拟机将这种情况称为"没必要执行".
- 如果这个对象被潘伟有必要执行finalize()方法,那么JVM会把这个对象放置F-Queue的队列中,然后一个由JVM自动建立的、低优先级的Finalizer线程去触发这个方法,但是不会承诺等待运行结束。
- finalize()方法是对象逃脱死亡的最后一次机会,GC会对F-Queue中的对象进行第二次小规模标记,如果第二次标记时没有被移除出"即将回收的集合" 就真的被回收了。
引用
强引用
- 指程序代码中普遍存在的,类似Object obj = new Object() 这类的引用,这要强引用 还在存在,垃圾收集器就永远不会回收掉被引用的对象。宁愿抛出OOM异常 也不会回收
弱引用
- 也是以用来描述非必须的对象,但是他的强度比软引用更弱一点,弱引用关联的对象只能生存到下一次垃圾收集发生之前。当GC 开始工作,无论当前内存是否足够,都会回收被弱引用关联的对象,在
- JDK1.2之后 用WeakReference 来实现弱引用
软引用
- 用来描述一些还有用但并非必须的对象
- 在系统将要发生内存溢出的异常之前,GC 会将这些对象列入回收范围之中进行第二次回收,如果这次回收了还没有足够的内存,才会抛出内存溢出异常。
- JDK1.2 之后提供 SoftReference类来实现软引用
虚引用
- 也称幻像引用,在是最弱的一种引用关系。
- 一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过一个虚引用来获取一个对象实例。
- 唯一目的就是在这个对象被垃圾回收之前收到一个系统通知
- JDK1.2之后提供PhantomReference类 来实现虚引用。
回收方法区
- 永久代的垃圾回收主要回收 废弃常量 和无用的类。
垃圾收集算法
标记-清除算法(Mark-Sweep)
- 分为 标记 和清除两个阶段
- 不足
- 效率问题,标记和清除的两个过程的效率不搞
- 空间问题,标记清楚之后会产生大量的不连续的内存碎片。
复制算法(Copying)
- 为了解决效率问题
- 它将内存容量划分为大小相等的两块,每次只使用其中的一块。当其中一块内存用完了 就将还存在活的对象复制到另一块上,然后把使用过的内存空间一次清理掉。
- 代价是 内存虽小为原来的一半。
- 新生代 使用了这个
标记整理算法
- 复制手机算法 会在对存活率较高的时候进行比较多的复制操作,效率会变低,如果不想浪费50%的空间 就需要额外的空间进行分配担保。以应付使用的内存所有对象都100%存活的极端情况。
- 老年代使用本算法
- 过程和标记-清除算法一样,但是后续不是可回收对象进行清理,而是让所有存活对象想一端移动,然后直接清理掉端边界以外的内存。
分代收集算法
- 商业虚拟机都采用本算法。
- 将对象存活周期的不同将内存划分为几块,一般把java堆分为新生代和老年代。这样根据各个年代的特点采用最适合的收集算法。
- 新生代 每次垃圾收集都会有大量对象死去,只有少量存活,就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集, 而老年代因为对象存活率高,没有额外的空间对它进行分配担保,就必须使用标记–整理 或者标记–清除 算法来进行回收。
垃圾收集器
Serial 收集器
- 最基本、发展历史最悠久的收集器
- 单线程的收集器,每次工作的时候就必须暂停其他所有工作线程,直到收集结束。
- 会触发 Stop The World
- Client模式 下默认的新生代的收集器。
- 简单高效
ParNew 收集器
- Serial 收集器的 多线程版本。
- 行为包括所有Serial收集器可用的控制参数( -XX:SurviorRatio -XX:PretenureSizeThreshold -XX:HandlePromotionFailure)、收集算法、Stop The World、对象分配规则、回收策略
- ParNew 是Server模式下虚拟机的首选的新生代手机袋。
- 可用配合CMS 收集器一起工作。
Parallel Scavenge 收集器
- 新生代收集器,复制算法,并行的多线程收集器。
- CMS收集器关注与停顿时间,而本收集器目标这是可控制的吞吐量
- 吞吐量= 运行用户代码时间/(运行用户代码时间+垃圾收集实现)
Serial Old 收集器
- Serial Old 是Serial 收集器的老年代版本,使用标记–整理算法,单线程
- 给Cilent 使用
Parallel Old 收集器
- Parallel Scavenge 收集器的老年代版本 使用 多线程和标记–整理算法。
CMS收集器
- 以获取最短回收停顿时间为目标的收集器
- 标记–清除算法
- 初始化标记 ( STW)
- 标记 GC roots 能关联到的对象
- 并发标记
- 进行 GC roots Tracing的过程
- 重新标记 (STW)
- 修正并发标记期间 用户程序继续运行而导致标记产生变动的标记记录
- 比初始化标记阶段长,但是比并发标记阶段短
- 并发清除
- 初始化标记 ( STW)
- 缺点
- 对CPU资源非常敏感
- 无法处理浮动垃圾
- 可能出现 Concurrent Mode Failure 失败 而导致另一次 Full GC 的产生
- 因为是基于标记–清除算法实现所以 收集结束可能产生大量的空间碎片。如果给大对象分配不了连续空间,不得不提前触发 Full GC
- 解决方案:
- CMS提供了一个 -XX:+UseCMSCCompactAtFullCollection开关参数(默认开启),用于CMS收集器顶不住进行Full GC的时候开启内存碎片的合并整理过程。
- 解决方案:
G1 收集器
-
JDK1.7 中 HotSpot 虚拟机的一个重要进化特征。
-
面向服务端的垃圾收集器
-
并发与并行
- 充分利用CPU 多核环境,让CPU来缩短STW的停顿时间。
-
分代收集
-
空间整理
- 与CMS的标记–清理算法不同,G1 可以看做是基于 标记–整理算法实现的收集器
-
可预测的停顿
- 除了追求低停顿外,还能建立可预测时间模型,能让使用
-
G1的运行
- 初始标记
- 修改TAMS值
- 并发标记
- 通过可达性分析,找出存活的的对象
- 最终标记
- 筛选回收
- 对Rcgion 的回收价值和成本排序根据GC停顿时间来制定回收计划。
- 初始标记
内存分配和回收策略
对象优先在Eden 分配
- 对象在新生代Eden区分配,当Eden区没有足够空间进行分配的时候,触发一次Minor GC
大对象直接进入老年代
- 大对象指 需要大量连续内存空间的JAVA对象
- JVM 提供 -XX:PretenureSizeThreshold 参数来设置直接在老年代分配。
- 作用是避免Eden区 和两个Survivor区之间发生大量的内存复制。
- 新生代采用 复制算法收集内存
长期存活的对象
- 虚拟机给每个对象定义了一个对象年龄计数器,如果对象在Eden畜生并且经历过第一次Minor GC后任然存活,并且能被Survivor弄那的话 被移动到Surivor空间中,并且设置对象年龄为1。 对象每次在Survivor区熬过一次Minor GC 年龄就会加1 当增长到15岁的时候 就会放入老年代中。阈值可以通过 -XX:MaxTenuringThreshold 设置
动态年龄判断
- JVM并不是一定要去对象的年龄到达默认的15岁 才能晋升老年代,如果Survivor空间中相同年龄所有对象大小 总和大于Survivor空间的一半,年龄大于或者等于该年龄的对象 直接进入老年代,无需计算年龄。
空间分配担保
- 发生Minor GC 之前,JVM会检查老年代最大可用的连续空间是否大于新生代所有对象的总空间,如果条件成立,Minor GC可用确保是安全的。 如果不成立 虚拟机会查看HandlePromotionFailure 设置值是否允许担保失败,如果允许,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于则尝试进行一次Minor GC ( 有风险) 如果小于 或者设置为不冒险,则需要进行一次Full GC