必须显式调用dispose()释放graphics2d,因其依赖系统级绘图资源,gc无法自动回收;同时需及时flush并置空bufferedimage及logo图像,避免句柄堆积和内存泄漏。

核心问题在于 Graphics2D 是 Java AWT 的原生图形上下文,其底层依赖系统级绘图资源(如 X11 Surface、Windows GDI+ 句柄),不显式释放会导致句柄堆积、内存持续增长,最终触发 OutOfMemoryError 或界面卡顿。这不是 GC 能自动回收的堆内存,而是 JVM 无法直接管理的本地资源。
必须显式调用 dispose() 的三个关键对象
Graphics2D 本身、它所绘制到的 BufferedImage、以及加载水印用的 BufferedImage(尤其是带 Alpha 的 Logo)都需手动释放:
- 每次完成绘图后立即调用
g2d.dispose(),不能依赖 try-with-resources(Graphics2D 不实现 AutoCloseable) - 主图 BufferedImage 在保存完毕、不再需要时设为
null,并建议在 finally 块中强制置空 - Logo 图片加载后若只用于单次绘制,绘制完立刻
logoImage.flush()+logoImage = null;若复用,也应在批量结束时统一清理
避免创建冗余 Graphics2D 实例
不要在循环内反复调用 bufferedImage.createGraphics() 后又忘记释放——这是泄漏最常见源头:
- 一个 BufferedImage 对应一个 Graphics2D,用完即弃,禁止跨图片复用同一 g2d
- 若需多次绘制(如叠加多个水印),应在同一 g2d 上连续操作,而不是反复获取再释放
- 慎用
getGraphics()(来自 JLabel 等组件),它返回的是临时上下文,生命周期不可控,应改用离屏 BufferedImage + createGraphics 流程
批量处理时采用流式单图模型
一次性加载几百张图到内存是高危操作,尤其处理高清图时极易突破 JVM 堆上限:
- 遍历文件列表,对每张图:读取 → 创建画布 → 绘制 → 保存 → 显式释放 → 进入下一张
- 不用 List
缓存全部图像,避免堆内存雪球式增长 - 对超大图(如 >5000px 边长),可先用 ImageInputStream 解析尺寸,再按需采样缩放,减少初始内存占用
检查并规避隐式资源绑定
某些看似无害的操作会悄悄持有资源引用:
-
BufferedImage.getSubimage()返回子图仍共享原图 DataBuffer,原图无法被 GC;应改用Raster.createChild()或重绘到新 BufferedImage - 使用
Image.getScaledInstance()时,若源图已 dispose 或 flush,可能引发 NPE 或资源错乱;务必确保缩放前源图有效且未释放 - 自定义 Font 或 Color 实例虽不直接泄漏,但大量创建未复用会加重 GC 压力,建议池化常用字体










