内容简介:下面是问题代码的简化版本每次new Font()之后,调用g.drawString()方法都会在Non-Heap区域分配一块内存且不回收g.drawString()的调用栈如下,
下面是问题代码的简化版本
public class FontMain { public static void main(String[] args) throws IOException, FontFormatException, InterruptedException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { File file = new File("/Users/cayun/PingFang.ttc"); while (true) { run(file); Thread.sleep(1); } } private static void run(File file) throws IOException, FontFormatException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { BufferedImage blankImage = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB); Graphics2D g = blankImage.createGraphics(); Font font = Font.createFont(Font.TRUETYPE_FONT, file); font = font.deriveFont(12.0f); g.setFont(font); g.drawString("hello", 12, 12); } } 复制代码
泄漏原因
原因概述
每次new Font()之后,调用g.drawString()方法都会在Non-Heap区域分配一块内存且不回收
调用栈
g.drawString()的调用栈如下,
SunGraphics2D.drawString(String, int, int)
-> ValidatePipe.drawString(SunGraphics2D, String, double, double)
-> SunGraphics2D.getFontInfo()
-> SunGraphics2D.checkFontInfo
-> Font2D.getStrike(Font, AffineTransform, AffineTransform, int, int)
-> Font2D.getStrike(FontStrikeDesc, boolean)
-> FileFont.createStrike(FontStrikeDesc)
-> ... -> T2KFontScaler.<init>(Font2D, int, boolean, int)
-> T2KFontScaler.initNativeScaler(...)
根本原因
在调用栈中第二个标红的部分
new T2KFontScaler() 时会调用 T2KFontScaler.initNativeScaler()这个native方法,这个native方法会在Non-Heap部分分配内存,且之后也没有相应的回收机制。
demo代码&效果图
public class FontMain { public static void main(String[] args) throws IOException, FontFormatException, InterruptedException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, ClassNotFoundException, InstantiationException { File file = new File("/System/Library/Fonts/AquaKana.ttc"); Font font = Font.createFont(Font.TRUETYPE_FONT, file); font = font.deriveFont(12.0f); BufferedImage blankImage = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB); Graphics2D g = blankImage.createGraphics(); g.setFont(font); // T2KFontScaler无法通过new的方式创建,此处使用反射创建 Class clazz = Class.forName("sun.font.T2KFontScaler"); Constructor constructor = clazz.getConstructor(Font2D.class, int.class, boolean.class, int.class); constructor.setAccessible(true); while (true) { constructor.newInstance(((SunGraphics2D) g).getFontInfo().font2D, 0, true, 80005872); Thread.sleep(1); } } } 复制代码
辅助证明:JDK已知bug
JDK-7074159 : run out of memory
解决方案
为字体做个缓存
public class FontMain { private static Font font = null; private static Object lock = new Object(); public static void main(String[] args) throws IOException, FontFormatException, InterruptedException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { File file = new File("/Users/cayun/PingFang.ttc"); while (true) { run(file); Thread.sleep(1); } } private static void run(File file) throws IOException, FontFormatException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { BufferedImage blankImage = new BufferedImage(100, 100, BufferedImage.TYPE_INT_RGB); Graphics2D g = blankImage.createGraphics(); if (font == null) { synchronized (lock) { if (font == null) { font = Font.createFont(Font.TRUETYPE_FONT, file); } } } font = font.deriveFont(12.0f); g.setFont(font); g.drawString("hello", 12, 12); } } 复制代码
原因详解
这个解决方法看起来有点奇怪,或许很容易就会有这样一个疑问:明明导致内存泄漏的是g.drawString()方法,却为何要对Font做缓存?
为了简单说明原因,我们先定义两种方案
- 方案1: 不使用缓存,就是原先会导致内存泄漏的方案
- 方案2: 对字体做缓存
以上所述就是小编给大家介绍的《记一次Font导致JVM堆外内存泄漏分析》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- Hadoop 不需认证导致数据泄漏
- 苍翼之刃:论File Descriptor泄漏如何导致Crash?
- 一文带你了解如何排查内存泄漏导致的页面卡顿现象
- 每日一道面试题(第三期)---一般什么情况下会导致内存泄漏问题
- 我的程序跑了60多小时,就是为了让你看一眼JDK的BUG导致的内存泄漏。
- Android 系统开发_内存泄漏篇 -- "内存泄漏"的前世今生
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。