You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

加载TTF字体时触发java.awt.FontFormatException空指针异常的解决

Java字体重复加载偶发NullPointerException异常分析与解决

问题重现

多次加载同一字体文件时,偶发抛出包含NullPointerException的FontFormatException,异常堆栈如下:

Caused by: java.awt.FontFormatException: java.lang.NullPointerException
    at java.desktop/sun.font.TrueTypeFont.init(TrueTypeFont.java:614)
    at java.desktop/sun.font.TrueTypeFont.<init>(TrueTypeFont.java:219)
    at java.desktop/sun.font.TrueTypeFont.<init>(TrueTypeFont.java:199)
    at java.desktop/sun.font.SunFontManager.createFont2D(SunFontManager.java:2356)
    at java.desktop/java.awt.Font.<init>(Font.java:621)
    at java.desktop/java.awt.Font.createFont(Font.java:1225)

加载代码示例:

public static void main(String ...args) {
    File fontFile = new File("arial.ttf");
    Font font = Font.createFont(Font.TRUETYPE_FONT, fontFile); // 此处触发异常
}

异常原因

  1. 底层字体初始化的线程安全问题:Sun/Oracle JDK的SunFontManager和TrueTypeFont内部实现未完全处理并发加载场景,多线程重复加载同一字体时,内部缓存或资源引用会出现竞态条件,导致初始化阶段某个核心对象为null,进而触发NPE并被包装为FontFormatException。
  2. 文件资源竞争问题:重复调用createFont(File)时,若文件句柄未被正确释放,或系统文件缓存存在竞争,可能导致读取到不完整的字体数据,间接引发初始化失败。

解决方案

1. 缓存已加载的字体实例(推荐)

第一次加载后将Font对象存入线程安全的缓存,后续直接复用,彻底避免重复加载操作:

import java.awt.Font;
import java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class FontLoader {
    private static final Map<String, Font> FONT_CACHE = new ConcurrentHashMap<>();

    public static Font getFont(String filePath) throws IOException, FontFormatException {
        return FONT_CACHE.computeIfAbsent(filePath, path -> {
            try {
                return Font.createFont(Font.TRUETYPE_FONT, new File(path));
            } catch (IOException | FontFormatException e) {
                throw new RuntimeException("加载字体文件失败: " + path, e);
            }
        });
    }
}

2. 同步加载逻辑

如果必须重复加载,通过同步锁避免多线程并发加载同一文件:

import java.awt.Font;
import java.io.File;
import java.io.IOException;

public class FontLoader {
    private static final Object LOAD_LOCK = new Object();

    public static Font loadFont(String filePath) throws IOException, FontFormatException {
        synchronized (LOAD_LOCK) {
            return Font.createFont(Font.TRUETYPE_FONT, new File(filePath));
        }
    }
}

3. 使用输入流并确保资源释放

改用FileInputStream加载字体,手动控制流的关闭,减少文件资源泄漏风险:

import java.awt.Font;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

public class FontLoader {
    public static Font loadFont(String filePath) throws IOException, FontFormatException {
        try (FileInputStream fis = new FileInputStream(new File(filePath))) {
            return Font.createFont(Font.TRUETYPE_FONT, fis);
        }
    }
}

内容的提问来源于stack exchange,提问作者John

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 06:37:05