设置单个行高时 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 消耗问题,此外还最终为我提供了一个如何正确计算单元格高度的工作示例。
我需要在 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 消耗问题,此外还最终为我提供了一个如何正确计算单元格高度的工作示例。