是什么导致 Jasper Reports 读取字体数据时抛出 java.io.IOException?

Lig*_*Dye 4 java x11 fonts jasper-reports

我正在测试环境上运行一个进程,该进程需要 10 多个小时才能运行并使用 Jasper Reports v3.7.5 生成 PDF 文档。

进程经常成功完成,但在某些情况下进程失败并抛出此异常:

20/05/2017 02:45:23.503 ERROR [process-pool-2-thread-20]  net.sf.jasperreports.extensions.DefaultExtensionsRegistry - Error instantiating extensions registry for simple.font.families
net.sf.jasperreports.engine.JRRuntimeException: java.io.IOException: Problem reading font data.
    at net.sf.jasperreports.engine.fonts.SimpleFontFace.<init>(SimpleFontFace.java:77)
    at net.sf.jasperreports.engine.fonts.SimpleFontFamily.createFontFace(SimpleFontFamily.java:316)
    at net.sf.jasperreports.engine.fonts.SimpleFontFamily.setNormal(SimpleFontFamily.java:85)
    at net.sf.jasperreports.engine.fonts.SimpleFontExtensionHelper.parseFontFamily(SimpleFontExtensionHelper.java:233)
    at net.sf.jasperreports.engine.fonts.SimpleFontExtensionHelper.parseFontFamilies(SimpleFontExtensionHelper.java:204)
    at net.sf.jasperreports.engine.fonts.SimpleFontExtensionHelper.loadFontFamilies(SimpleFontExtensionHelper.java:173)
    at net.sf.jasperreports.engine.fonts.SimpleFontExtensionHelper.loadFontFamilies(SimpleFontExtensionHelper.java:142)
    at net.sf.jasperreports.engine.fonts.SimpleFontExtensionsRegistryFactory.createRegistry(SimpleFontExtensionsRegistryFactory.java:63)
    at net.sf.jasperreports.extensions.DefaultExtensionsRegistry.instantiateRegistry(DefaultExtensionsRegistry.java:238)
    at net.sf.jasperreports.extensions.DefaultExtensionsRegistry.loadRegistries(DefaultExtensionsRegistry.java:213)
    at net.sf.jasperreports.extensions.DefaultExtensionsRegistry.loadRegistries(DefaultExtensionsRegistry.java:162)
    at net.sf.jasperreports.extensions.DefaultExtensionsRegistry.getRegistries(DefaultExtensionsRegistry.java:132)
    at net.sf.jasperreports.extensions.DefaultExtensionsRegistry.getExtensions(DefaultExtensionsRegistry.java:104)
    at net.sf.jasperreports.engine.util.JRStyledTextParser.<clinit>(JRStyledTextParser.java:76)
    at net.sf.jasperreports.engine.fill.JRBaseFiller.<init>(JRBaseFiller.java:182)
    at net.sf.jasperreports.engine.fill.JRVerticalFiller.<init>(JRVerticalFiller.java:77)
    at net.sf.jasperreports.engine.fill.JRVerticalFiller.<init>(JRVerticalFiller.java:87)
    at net.sf.jasperreports.engine.fill.JRVerticalFiller.<init>(JRVerticalFiller.java:57)
    at net.sf.jasperreports.engine.fill.JRFiller.createFiller(JRFiller.java:142)
    at net.sf.jasperreports.engine.fill.JRFiller.fillReport(JRFiller.java:78)
    at net.sf.jasperreports.engine.JasperFillManager.fillReport(JasperFillManager.java:624)
    at net.sf.jasperreports.engine.JasperFillManager.fillReport(JasperFillManager.java:605)
    ...
Caused by: java.io.IOException: Problem reading font data.
    at java.awt.Font.createFont0(Font.java:1000)
    at java.awt.Font.createFont(Font.java:877)
    at net.sf.jasperreports.engine.fonts.SimpleFontFace.<init>(SimpleFontFace.java:69)
    ... 120 common frames omitted       
Run Code Online (Sandbox Code Playgroud)

不久之后,在同一线程上,记录以下错误。但我不确定它是否相关:

20/05/2017 02:45:23.605 ERROR [process-pool-2-thread-20]  my.package.MyClass.NoClassDefFoundError - AbstractReportCreationService.createAndPersistReport(...) threw an error: Could not initialize class sun.awt.X11GraphicsEnvironment
Run Code Online (Sandbox Code Playgroud)

我尝试在 Solaris 5.10 和 5.11 上运行此过程,使用相同的 Java 1.8.0 版本。它在两者上都是随机发生的。我一直在尝试重现该错误以找到根本原因,但到目前为止无济于事。

我读过 StackOverflow 和 Jaspersoft 社区论坛中报告的类似问题。这些帖子中的大多数都提到了可能阻止进程读取或写入 java 临时目录 ( java.io.tmpdir = /var/tmp/) 的权限问题。参见示例:

JasperFillManager.fillReport()方法抛出java.io.Exception

我已经检查了文件权限,这些都是正确的。当该进程成功运行时,它会创建名为类似+~JF9070759829719582131.tmp或类似随机名称的临时字体文件。即使该过程失败,它也会在/var/tmp/目录中留下一些临时字体文件,因此权限似乎不是问题。最重要的是,成功和失败的运行之间的权限不会改变。

在 Jaspersoft 社区论坛的这篇文章中:

http://community.jaspersoft.com/questions/543492/javaioioexception-problem-reading-font-data

建议的解决方案是使用选项启动 Tomcat

-Djava.awt.headless=true
Run Code Online (Sandbox Code Playgroud)

我正在运行的进程使用普通 Java,而不是 Tomcat,但我愿意尝试无头模式。由于问题是随机发生的并且需要很长时间才能运行,因此很难证明这种可能的解决方案确实可以解决问题。我担心将其部署到生产环境并随机出现相同的问题。任何人都可以解释为什么在无头模式下运行可能会解决问题,或者我应该尝试其他可能的解决方案吗?

小智 6

我们\xe2\x80\x99在我们的代码中解决了这个完全相同的问题。

\n\n

它会随机发生。我们解决了实际问题被 java 吞没并伪装在 Exception 下

\n\n
Caused by: java.io.IOException: Problem reading font data.\n                at java.awt.Font.createFont0(Font.java:1000)\n                at java.awt.Font.createFont(Font.java:877)\n                at net.sf.jasperreports.engine.fonts.SimpleFontFace.<init>(SimpleFontFace.java:69)\n                ... 120 common frames omitted \n
Run Code Online (Sandbox Code Playgroud)\n\n

因此,我编写了一段代码,它可以复制创建字体所需的所有功能,但不会\xe2\x80\x99t吞掉真正的异常。我们在屏幕(我们可以附加和分离的服务器端会话)中运行该过程 24 小时,没有出现任何问题。

\n\n

直到我们让最初启动问题函数的用户在屏幕中执行新的测试函数为止。产生了以下错误 -

\n\n
java.io.IOException: Problem reading font data.\n        at judson.Main.createFont0(Main.java:203)\n        at judson.Main.createFont(Main.java:94)\n        at judson.Main.createFont(Main.java:49)\n        at judson.Main.main(Main.java:39)\nCaused by: java.awt.AWTError: Can\'t connect to X11 window server using \'localhost:12.0\' as the value of the DISPLAY variable.\n        at sun.awt.X11GraphicsEnvironment.initDisplay(Native Method)\n
Run Code Online (Sandbox Code Playgroud)\n\n

事实证明,用户正在使用 putty 管理器,它会自动设置 DISPLAY 变量。

\n\n
/home/user/judsona/tmp/FontErrorDetector/bin : echo $DISPLAY\nlocalhost:15.0\n
Run Code Online (Sandbox Code Playgroud)\n\n

进一步调查为什么它只是偶尔失败,我们发现当会话可用时(X11可用)它会工作,但一旦会话断开连接它就会失败。注意:即使发起用户从屏幕会话中分离,也会发生这种情况,它会等到分离后关闭会话,然后才会失败

\n\n

所以我研究了 GraphicsEnvironment 代码 & X11GraphicsEnvironment

\n\n

X11GraphicsEnvironment.java

\n\n
static {\n    java.security.AccessController.doPrivileged(\n                      new java.security.PrivilegedAction() {\n        public Object run() {\n            System.loadLibrary("awt");\n\n            /*\n             * Note: The MToolkit object depends on the static initializer\n             * of X11GraphicsEnvironment to initialize the connection to\n             * the X11 server.\n             */\n            if (!isHeadless()) {\n                // first check the OGL system property\n                boolean glxRequested = false;\n                String prop = System.getProperty("sun.java2d.opengl");\n                if (prop != null) {\n                    if (prop.equals("true") || prop.equals("t")) {\n                        glxRequested = true;\n                    } else if (prop.equals("True") || prop.equals("T")) {\n                        glxRequested = true;\n                        glxVerbose = true;\n                    }\n                }\n\n                // Now check for XRender system property\n                boolean xRenderRequested = true;\n                boolean xRenderIgnoreLinuxVersion = false;\n                String xProp = System.getProperty("sun.java2d.xrender");\n                    if (xProp != null) {\n                    if (xProp.equals("false") || xProp.equals("f")) {\n                        xRenderRequested = false;\n                    } else if (xProp.equals("True") || xProp.equals("T")) {\n                        xRenderRequested = true;\n                        xRenderVerbose = true;\n                    }\n\n                    if(xProp.equalsIgnoreCase("t") || xProp.equalsIgnoreCase("true")) {\n                        xRenderIgnoreLinuxVersion = true;\n                    }\n                }\n\n                // initialize the X11 display connection\n                initDisplay(glxRequested); \n
Run Code Online (Sandbox Code Playgroud)\n\n

^ 当它确定它不是\xe2\x80\x99t无头时,它会initDisplay。

\n\n

图形环境.java

\n\n
 /**\n * @return the value of the property "java.awt.headless"\n * @since 1.4\n */\nprivate static boolean getHeadlessProperty() {\n    if (headless == null) {\n        java.security.AccessController.doPrivileged(\n        new java.security.PrivilegedAction<Object>() {\n            public Object run() {\n                String nm = System.getProperty("java.awt.headless");\n\n                if (nm == null) {\n                    /* No need to ask for DISPLAY when run in a browser */\n                    if (System.getProperty("javaplugin.version") != null) {\n                        headless = defaultHeadless = Boolean.FALSE;\n                    } else {\n                        String osName = System.getProperty("os.name");\n                        if (osName.contains("OS X") && "sun.awt.HToolkit".equals(\n                                System.getProperty("awt.toolkit")))\n                        {\n                            headless = defaultHeadless = Boolean.TRUE;\n                        } else {\n                            headless = defaultHeadless =\n                                Boolean.valueOf(("Linux".equals(osName) ||\n                                                 "SunOS".equals(osName) ||\n                                                 "FreeBSD".equals(osName) ||\n                                                 "NetBSD".equals(osName) ||\n                                                 "OpenBSD".equals(osName)) &&\n                                                 (System.getenv("DISPLAY") == null));\n                        }\n                    }\n               } else if (nm.equals("true")) {\n                    headless = Boolean.TRUE;\n                } else {\n                    headless = Boolean.FALSE;\n                }\n                return null;\n            }\n            }\n        );\n    }\n    return headless.booleanValue();\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

当设置 DISPLAY 变量时,java 确定它不是无头的,这意味着它将尝试建立与 X11 服务器的连接(当用户关闭其会话导致 GraphicsEnvironment 无法启动时,该连接将断开)

\n\n

结论\xe2\x80\x93

\n\n

你有三个选择

\n\n
    \n
  1. 使用 -Djava.awt.headless=true 启动应用程序
  2. \n
  3. 在未设置 DISPLAY 的情况下启动您的应用程序。
  4. \n
  5. 确保没有人使用自动设置 X11 DISPLAY 变量的 putty 管理器。
  6. \n
\n\n

我个人会使用 #1 -Djava.awt.headless=true

\n