記一次Font致使JVM堆外內存泄漏分析

原由

雙11期間,公司的某個Java服務內存佔用達到37g,可是該應用的JVM配置爲-Xms6g -Xmx6gjava

問題分析

業務

主要是涉及到了圖片文字合成業務緩存

代碼

下面是問題代碼的簡化版本bash

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()的調用棧以下,spa

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(...)code

根本緣由

在調用棧中第二個標紅的部分orm

new T2KFontScaler() 時會調用 T2KFontScaler.initNativeScaler()這個native方法,這個native方法會在Non-Heap部分分配內存,且以後也沒有相應的回收機制。cdn

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);
        }
    }
}
複製代碼

image-20181127183725026

輔助證實: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作緩存?blog

爲了簡單說明緣由,咱們先定義兩種方案

  1. 方案1: 不使用緩存,就是原先會致使內存泄漏的方案
  2. 方案2: 對字體作緩存

具體緣由

如今來看調用棧部分第一個標紅的位置,源碼以下

image-20181122122215650

快速判斷此類問題的方法

jmap -histo <pid> | grep FontScaler
複製代碼

若是該對象特別多,那極大多是因爲這個緣由致使

相關文章
相關標籤/搜索