【问题标题】:JTable memory leak when setting individual row heights设置单个行高时 JTable 内存泄漏
【发布时间】:2020-09-03 11:44:31
【问题描述】:

我需要在 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;
    }
}

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

通过注释掉选定的代码部分,我将这些行确定为罪魁祸首:

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

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

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

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

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

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

【问题讨论】:

  • 我看到 AWT 工作线程开始最大限度地使用一个 CPU 核心。 - 更改 JTable 的属性将导致表重新绘制自身。您可能会陷入无限循环。您是否验证过调用table.setRowHeight(…) 方法的频率?发布 minimal reproducible example 来展示您的问题。
  • 您使用什么监控工具显示高内存使用情况?另外,正如@camickr 所说,没有minimal reproducible example,我们所能做的就是猜测。
  • table.setRowHeight(…) 每当MultiLineCellRenderer#getTableCellRendererComponent() 被调用或Swing 在内部调用它时都会被调用。我不会在我的代码中的其他任何地方调用它。

标签: java swing memory-leaks jtable


【解决方案1】:

设置行高会触发重绘,进而触发对渲染器的另一个调用。因此,只有当行高不同于当前行高时才设置行高,以避免出现 endles 循环,这一点很重要。当您无条件调用setRowHeight() 时会发生这种情况,即使是固定值。

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

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

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

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

底线:使用多行单元格渲染 JTable 并非易事,而且 SO 有很多错误示例。我发现的唯一有效示例(感谢另一篇 SO 帖子)位于 https://www.javaspecialists.eu/archive/Issue106.html

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

除了最后给我一个如何正确计算单元高度的工作示例之外,这还解决了内存泄漏/处理器消耗问题。

【讨论】:

    猜你喜欢
    • 2011-10-08
    • 2013-04-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-06
    • 1970-01-01
    • 1970-01-01
    • 2011-10-17
    相关资源
    最近更新 更多