内存泄漏举例:
-
单例模式
- 单例的生命周期和应用程序一样长,如果持有对外部对象的引用,则这些外部对象无法被回收,引用过多则可能产生内存泄漏。
-
未关闭的资源
- 数据库连接(
dataSource.getConnection()
)、网络连接(socket
)和IO连接必须手动调用close()
,否则这些资源无法被回收,导致内存泄漏。
- 数据库连接(
1. 安全点(Safepoint)
程序运行时停顿下来开始安全GC的位置。通常选择执行时间较长的指令作为安全点,例如:方法调用、循环跳转、异常跳转等。
2. 安全区域(Safe Region)
当线程处于Sleep
或Blocked
状态时,无法响应JVM的中断请求走到安全点,但此时对象的引用关系不会发生变化,则此区域内任何位置开始GC都是安全的,此区域即为安全区域。安全区域设计用于处理非活动线程的GC安全问题。
3. 线程中断方式
- 抢先式中断(已废弃)
- 中断所有线程,若线程不在安全点,则恢复线程并让其运行到安全点。
- 主动式中断(主流实现)
- 线程在运行到安全点(轮询点)时检查一个全局标志位(如Safepoint Polling Page),即轮询标志,如果发现 JVM 要求暂停,则线程自行挂起,等待GC完成。
JVM内存页保护机制实现高效轮询:
-
机制:设置一个轮询页(Polling Page),正常情况下,该页可读可写;当需要触发 GC时,将该页设为不可访问(触发 SIGSEGV 信号)。
-
线程行为:线程到达轮询点时访问该页
- GC 未触发 -> 访问成功,线程继续执行。
- GC 已触发 -> 访问失败,线程进入信号处理程序,挂起并等待 GC 完成。
-
优点:
- 零开销:在非 GC 阶段,轮询只是一个普通内存读取,几乎没有性能损耗。
- 即时响应:一旦 GC 触发,线程会在下一个轮询点快速挂起。
4. 完整流程
- JVM 发起 GC 请求,设置全局标志(如修改 Polling Page 权限)。
- 运行中的线程 在下一个轮询点检查到标志不可访问,挂起自己。
- 阻塞的线程 已在 Safe Region 内,无需额外操作。
- 所有线程暂停后,JVM 执行 GC。
- GC 完成后,恢复线程执行。