触发条件
内存垃圾回收机制主要集中的区域就是线程共享区域:堆和方法区
Minor GC 触发条件非常简单,当 Eden 空间满时,就将触发一次 Minor GC
FullGC 同时回收新生代、老年代和方法区,只会存在一个 FullGC 的线程进行执行,其他的线程全部会被挂起,有以下触发条件:
- 调用 System.gc():
- 在默认情况下,通过 System.gc() 或 Runtime.getRuntime().gc() 的调用,会显式触发 FullGC,同时对老年代和新生代进行回收,但是虚拟机不一定真正去执行,无法保证对垃圾收集器的调用
- 不建议使用这种方式,应该让虚拟机管理内存。一般情况下,垃圾回收应该是自动进行的,无须手动触发;在一些特殊情况下,如正在编写一个性能基准,可以在运行之间调用 System.gc()
- 老年代空间不足:
- 为了避免引起的 Full GC,应当尽量不要创建过大的对象以及数组
- 通过 -Xmn 参数调整新生代的大小,让对象尽量在新生代被回收掉不进入老年代,可以通过
-XX:MaxTenuringThreshold
调大对象进入老年代的年龄,让对象在新生代多存活一段时间
- 空间分配担保失败
- JDK 1.7 及以前的永久代(方法区)空间不足
- Concurrent Mode Failure:执行 CMS GC 的过程中同时有对象要放入老年代,而此时老年代空间不足(可能是 GC 过程中浮动垃圾过多导致暂时性的空间不足),便会报 Concurrent Mode Failure 错误,并触发 Full GC
安全区域
安全点 (Safepoint):程序执行时并非在所有地方都能停顿下来开始 GC,只有在安全点才能停下
- Safe Point 的选择很重要,如果太少可能导致 GC 等待的时间太长,如果太多可能导致运行时的性能问题
- 大部分指令的执行时间都非常短,通常会根据是否具有让程序长时间执行的特征为标准,选择些执行时间较长的指令作为 Safe Point, 如方法调用、循环跳转和异常跳转等
在 GC 发生时,让所有线程都在最近的安全点停顿下来的方法:
- 抢先式中断:没有虚拟机采用,首先中断所有线程,如果有线程不在安全点,就恢复线程让线程运行到安全点
- 主动式中断:设置一个中断标志,各个线程运行到各个 Safe Point 时就轮询这个标志,如果中断标志为真,则将自己进行中断挂起
问题:Safepoint 保证程序执行时,在不太长的时间内就会遇到可进入 GC 的 Safepoint,但是当线程处于 Waiting 状态或 Blocked 状态,线程无法响应 JVM 的中断请求,运行到安全点去中断挂起,JVM 也不可能等待线程被唤醒,对于这种情况,需要安全区域来解决
安全区域 :指在一段代码片段中,对象的引用关系不会发生变化,在这个区域中的任何位置开始 GC 都是安全的
运行流程:
- 当线程运行到 Safe Region 的代码时,首先标识已经进入了 Safe Region,如果这段时间内发生 GC,JVM 会忽略标识为 Safe Region 状态的线程
- 当线程即将离开 Safe Region 时,会检查 JVM 是否已经完成 GC,如果完成了则继续运行,否则线程必须等待 GC 完成,收到可以安全离开 SafeRegion 的信号