双11期间,公司的某个Java服务内存占用达到37g,可是该应用的JVM配置为-Xms6g -Xmx6g
java
主要是涉及到了图片文字合成业务缓存
下面是问题代码的简化版本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
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-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
为了简单说明缘由,咱们先定义两种方案
如今来看调用栈部分第一个标红的位置,源码以下
jmap -histo <pid> | grep FontScaler
复制代码
若是该对象特别多,那极大多是因为这个缘由致使