设置单个行高时 JTable 内存泄漏

JTable memory leak when setting individual row heights

我需要在 JTable 中显示多行内容。实际内容是在自定义模型中维护的对象集合,它扩展 DefaultTableModel 并通过覆盖 getValueAt().

动态生成单元格内容

为了多行内容,我实现了自定义TableCellRenderer:

private class MultiLineCellRenderer extends JTextArea implements TableCellRenderer {
    public MultiLineCellRenderer() {
        setLineWrap(true);
        setWrapStyleWord(true);
        setOpaque(true);
        setBorder(new EmptyBorder(-1, 2, -1, 2));
        setRows(1);
    }

    public Component getTableCellRendererComponent(JTable table, Object value,
            boolean isSelected, boolean hasFocus, int row, int column) {
        String text = value == null ? "" : value.toString();
        if (!getText().equals(text)) {
            setText(text);

            int newHeight = table.getRowHeight() * getLineCount();
            if (table.getRowHeight(row) != newHeight)
                table.setRowHeight(row, newHeight);
        }

        if (isSelected) {
            setForeground(table.getSelectionForeground());
            setBackground(table.getSelectionBackground());
        } else {
            setForeground(table.getForeground());
            setBackground(table.getBackground());
        }

        return this;
    }
}

现在,如果我用几百行填充 table(列数为 2),我会看到 AWT 工作线程开始最大化一个 CPU 核心。与此同时,内存消耗从大约 100 MB 上升到该数量的十倍甚至更多。即使应用程序实际上没有做任何事情(后台没有加载数据,没有用户交互)并且仅当我清除 table 从中获取其内容的集合时才停止,这种情况也会发生。

通过注释掉 select 部分代码,我确定这些行是罪魁祸首:

            int newHeight = table.getRowHeight() * getLineCount();
            if (table.getRowHeight(row) != newHeight)
                table.setRowHeight(row, newHeight);

如果我注释掉这部分,所有 table 行都具有相同的高度(1 行文本),但内存消耗保持在 ~100 MB 左右。

如果我将这些行替换为对 table.setRowHeight(row, 32) 的一次调用,即使用固定值,内存消耗将再次无限期地上升。

以下修改有效,但代价是所有行的高度相同:

            int newHeight = getRowHeight() * getLineCount();
            if (table.getRowHeight() < newHeight)
                table.setRowHeight(newHeight);

底线:在 JTable 中设置单独的行高似乎会造成大量内存泄漏。我做错了什么,还是遇到了实际的错误?在后一种情况下,是否有任何已知的 fixes/workarounds?

设置行高会触发重绘,这又会触发对渲染器的另一次调用。因此,重要的是仅当行高与当前行高不同时才设置行高,以避免无限循环。当您无条件地调用 setRowHeight() 时会发生这种情况,即使是使用固定值也是如此。

第二个问题是每行包含两个单元格,它们的高度可能不同。上面的代码将设置行高以匹配正在呈现的单元格。当该行的另一个单元格被渲染并具有不同的高度时,行高将再次更改。这将触发重绘,也是该行中第一列的重绘。因为这会导致另一个高度变化,所以又是无限循环。

证明:以下代码解决了这个问题:

            int newHeight = table.getRowHeight() * getLineCount();
            if (table.getRowHeight(row) < newHeight)
                table.setRowHeight(row, newHeight);

现在行高只会增加,不会减少,从而打破死循环。副作用:如果单元格内容发生变化,现在占用的行数比以前少,行高将不会改变以反映这一点。

底线:呈现具有多行单元格的 JTable 非常重要,SO 有很多错误示例。我找到的唯一有效示例(感谢另一个 SO post)位于 https://www.javaspecialists.eu/archive/Issue106.html.

他们的解决方案是在渲染器内部存储单元格高度(尽管这也可以在 table 模型中完成,以最适合您的实施方式为准)。在计算单元格的高度时,存储它,然后获取行中任何单元格的最大高度并使用它。另外,请务必仅在行高与当前行高不同时才设置行高。

这解决了内存 leak/processor 消耗问题,此外还最终为我提供了一个如何正确计算单元格高度的工作示例。