我有一个扩展JComponent的ImageViewComponent。此组件将添加到添加到JFrame的JPanel中。从另一个类我定期从另一个类更新ImageViewComponent的int[] image
字段。
问题是这个过程占用了大量的内存。它甚至消耗了如此多的内存(根据JProfiler几秒钟内+/- 130 MB,最终超过1GB)整个程序经历了一个“延迟峰值”。垃圾收集期间(程序中的延迟发生在清除内存的同时)。
这是ImageViewComponent的代码:
public class ImageViewComponent extends JComponent {
private int image_width, image_height, updateInterval, updateCounter;
private int[] imageArray;
private BufferedImage currentDisplayedImage;
private Image scaledDisplayedImage;
/**
* @param width The width of this component
* @param height The height of this component
* @param ui The higher, the less frequent the image will be updated
*/
public ImageViewComponent(int width, int height, int ui) {
setPreferredSize(new Dimension(width, height));
this.updateInterval = ui;
this.updateCounter = 0;
this.currentDisplayedImage = null;
this.scaledDisplayedImage = null;
}
public void setImage(int[] image, int width, int height) {
this.imageArray = image;
this.image_width = width;
this.image_height = height;
}
@Override
public void paint(Graphics g) {
super.paint(g);
if (image_width == 0 || image_height == 0)
return;
else if (updateCounter != updateInterval && currentDisplayedImage != null) {
g.drawImage(scaledDisplayedImage, 0, 0, this);
updateCounter++;
return;
}
this.currentDisplayedImage = new BufferedImage(image_width, image_height, BufferedImage.TYPE_INT_RGB);
this.currentDisplayedImage.setRGB(0, 0, image_width, image_height, this.imageArray, 0, image_width);
this.scaledDisplayedImage = this.currentDisplayedImage.getScaledInstance(this.getPreferredSize().width,
this.getPreferredSize().height, BufferedImage.SCALE_DEFAULT);
g.drawImage(scaledDisplayedImage, 0, 0, this);
// reset update counter
updateCounter = 0;
}
}
JProfiler声明70%的程序活动内存在此类中分配,50%在Graphics.drawImage
,而20%在BufferedImage初始化。
我尝试通过将行this.currentDisplayedImage = new BufferedImage(image_width, image_height, BufferedImage.TYPE_INT_RGB)
放在`setImage'中来修复它。并且只使用布尔标志设置它一次但是这使得绘制的图像偶尔会在短时间内完全变黑,也不会修复内存问题。
我也尝试了this建议,但也没有。
如何解决此内存问题?
答案 0 :(得分:3)
代码有几个问题。有些是指性能,有些是风格或最佳实践,有些(至少可能)是指内存消耗。
getScaledInstance
方法令人沮丧。有关更好的替代方案,请参阅https://stackoverflow.com/a/32278737/3182664和其他人imageWidth
,而不是image_width
JComponent
,您通常会覆盖paintComponent
而不是paint
正如MadProgrammer已经指出的那样:尽可能少做事。这个updateCounter
的作用和目的并不完全清楚。我认为用于更频繁更新图像的责任应该在使用组件的类中 - 特别是在调用updateImage
的类中(应该简单地做得少一些)。在paint
方法中维护这一点并不十分可靠。
在您当前的代码中,似乎currentDisplayedImage
(尽管它的名称)既没有显示也没有使用以任何其他方式。但是,保留它可能是个好主意:需要填充int[]
数据,并将其作为可能必须创建的缩放图像的来源。
您班级的一种可能实现方式如下:
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import javax.swing.JComponent;
public class ImageViewComponent extends JComponent {
private int updateInterval, updateCounter;
private BufferedImage fullImage;
private BufferedImage displayedImage;
/**
* @param width The width of this component
* @param height The height of this component
* @param ui The higher, the less frequent the image will be updated
*/
public ImageViewComponent(int width, int height, int ui) {
setPreferredSize(new Dimension(width, height));
this.updateInterval = ui;
this.updateCounter = 0;
this.fullImage = null;
this.displayedImage =
new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
}
public void setImage(int[] image, int width, int height) {
// Note: The updateInvervall/updateCounter stuff COULD
// probably also be done here...
if (fullImage == null ||
fullImage.getWidth() != width ||
fullImage.getHeight() != height)
{
fullImage = new BufferedImage(
width, height, BufferedImage.TYPE_INT_RGB);
}
fullImage.setRGB(0, 0, width, height, image, 0, width);
scaleImage(fullImage, displayedImage);
repaint();
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(displayedImage, 0, 0, this);
}
private static BufferedImage scaleImage(
BufferedImage input, BufferedImage output)
{
double scaleX = (double) output.getWidth() / input.getWidth();
double scaleY = (double) output.getHeight() / input.getHeight();
AffineTransform affineTransform =
AffineTransform.getScaleInstance(scaleX, scaleY);
AffineTransformOp affineTransformOp =
new AffineTransformOp(affineTransform, null);
return affineTransformOp.filter(input, output);
}
}
但注意,由于上述原因,不执行此“updateInterval”处理。
旁注:也许你甚至不需要缩放图像。如果您打算始终以组件大小显示,那么您可以简单地执行
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
// Draw the FULL image, which, regardless of its size (!)
// is here painted to just fill this component:
g.drawImage(fullImage, 0, 0, getWidth(), getHeight(), null);
}
通常,绘制这样的缩放图像非常快。但是,根据许多因素,像你一样分离缩放和绘画图像的步骤也可能是一个合理的选择。