JEditorPaneHTMLEditorKitParserOutOfMemory内存溢出

wen java案例 2

本文目录导读:

JEditorPaneHTMLEditorKitParserOutOfMemory内存溢出

  1. 问题根因分析
  2. 立即修复方案
  3. 运行时调优(临时缓解)
  4. 针对性代码检查清单
  5. 终极替代方案
  6. 快速诊断工具

这是一个典型的Java Swing组件JEditorPane配合HTMLEditorKit解析HTML时导致的内存溢出(OutOfMemoryError)问题,这类问题通常由畸形HTML、超大嵌套深度、或大量未关闭标签引发。

下面是针对该问题的系统性诊断与解决方案:

问题根因分析

HTMLEditorKit的底层解析器(ParserDelegator)是基于Swing的HTML 3.2/HTML 4.0的严谨解析器,它有以下弱点:

  • 递归解析:遇到未闭合的嵌套标签(如<div><div><div>...)会产生深度递归,迅速耗尽栈和堆。
  • 内存分配策略:它会预分配大量char[]String对象来处理标记符号,尤其是大文件或长属性值。
  • 非流式处理:整个HTML必须完全加载到内存中构建DOM树(Swing HTML Document),不支持SAX/流式解析。

立即修复方案

方案A:限制输入大小与标签深度

import javax.swing.text.html.*;
import javax.swing.text.html.parser.*;
// 自定义解析器,限制递归深度
public class SafeHtmlEditorKit extends HTMLEditorKit {
    // 最大允许的嵌套深度
    private static final int MAX_NESTING_DEPTH = 100;
    @Override
    public ViewFactory getViewFactory() {
        // 返回自定义ViewFactory,用于限制渲染深度
        return new SafeHTMLFactory();
    }
    private static class SafeHTMLFactory extends HTMLEditorKit.HTMLFactory {
        @Override
        public View create(Element elem) {
            // 检查元素深度,超过阈值返回空View
            int depth = getDepth(elem);
            if (depth > MAX_NESTING_DEPTH) {
                return new javax.swing.text.html.InlineView(elem) {
                    @Override
                    public float getPreferredSpan(int axis) {
                        return 0; // 不占用空间
                    }
                };
            }
            return super.create(elem);
        }
        private int getDepth(Element elem) {
            int depth = 0;
            while (elem != null) {
                depth++;
                elem = elem.getParentElement();
            }
            return depth;
        }
    }
}

使用:

JEditorPane editor = new JEditorPane();
editor.setEditorKit(new SafeHtmlEditorKit());

方案B:使用外部流式HTML解析器预处理

最干净的解决方案——不要用HTMLEditorKit解析长HTML,而是用Jsoup(成熟、流式安全)预处理成安全片段:

import org.jsoup.Jsoup;
import org.jsoup.safety.Safelist;
import org.jsoup.nodes.Document;
// Maven依赖: org.jsoup:jsoup:1.17.1
public String sanitizeHtml(String rawHtml) {
    // 1. 使用Jsoup解析(流式、非递归DOM构建)
    Document doc = Jsoup.parse(rawHtml, "UTF-8");
    // 2. 清理/转义(去掉危险标签和过度嵌套)
    //    basic() 保留: a, b, blockquote, br, cite, code, dd, dl, dt, em, i, li, ol, p, pre, q, small, span, strike, strong, sub, sup, u, ul
    String safeHtml = Jsoup.clean(doc.body().html(), Safelist.basic());
    // 3. 限制大小(比如前50KB)
    if (safeHtml.length() > 50 * 1024) {
        safeHtml = safeHtml.substring(0, 50 * 1024) + "...";
    }
    return safeHtml;
}

然后在JEditorPane中显示:

String safeHtml = sanitizeHtml(rawHtml);
editor.setText(safeHtml);

运行时调优(临时缓解)

如果无法修改代码,可以尝试调整JVM参数:

# 增加堆内存(至少给到512MB以上)
java -Xms512m -Xmx1024m -XX:+UseG1GC -XX:MaxInlineLevel=15 YourApp
# 或启用并发GC减少Full GC停顿
-XX:+UseConcMarkSweepGC

针对性代码检查清单

  • [x] 是否使用setText()而不是read()

    • read(Reader, ...) 不会重置解析器状态,累计内存,优先用setText()
  • [x] 是否反复更新同一个JEditorPane

    • 每次更新前调用editor.setText(null)清除旧文档,避免累积。
  • [x] 是否包含大量<table>嵌套?

    • 表格解析是已知的重度递归区域,建议用<div>+CSS替代。
  • [x] 是否使用<script><style>

    • HTMLEditorKit不会执行脚本,但会解析其内容,可能导致内存膨胀,清理掉这些标签。
  • [x] 是否以字符串方式动态拼接HTML?

    可能是拼接过程中产生了超大字符串(如循环拼接),检查拼接逻辑。

终极替代方案

如果以上方案都无法解决,建议彻底替换Swing控件

方案 优点 缺点
JavaFX WebView 现代、基于WebKit、安全、支持CSS3/JS 需要JavaFX运行时(JDK 11+内置)
SWT Browser 原生浏览器内核 平台相关、增加了SWT依赖
JTextPane + 自定义渲染 避免HTML解析,只支持RichText 功能有限

推荐迁移到JavaFX WebView(代码量小):

// 在Swing中嵌入JavaFX
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Scene;
import javafx.scene.web.WebView;
JFXPanel fxPanel = new JFXPanel();
Platform.runLater(() -> {
    WebView webView = new WebView();
    webView.getEngine().loadContent(yourHtmlString);
    fxPanel.setScene(new Scene(webView));
});
// 然后将fxPanel加入Swing容器

快速诊断工具

在本地复现时,可以开启JVM内存监控:

# 打印GC详情
-XX:+PrintGCDetails -XX:+PrintGCTimeStamps
# 启用内存溢出自动Dump(分析堆)
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heapdump.hprof

然后用 Eclipse MATVisualVM 分析heap dump,查找HTMLEditorKit.Parser相关的巨型对象。

优先级 行动 效果
使用Jsoup预处理HTML 彻底解决递归与畸形HTML问题
限制嵌套深度+大小截断 折衷,保留Swing组件
迁移至JavaFX WebView 长期可靠,但需要重构
调高JVM堆内存 仅临时缓解,不治本

最佳实践组合:Jsoup清理HTML + JEditorPane显示 -> 或 -> 直接使用JavaFX WebView。

抱歉,评论功能暂时关闭!