在 Java 中加载图像文件时出现内存不足异常

Outofmemory exception loading image files in Java

我在加载图像文件时遇到问题。 有2个案例,我测试过。问题只发生在第一个。在这两种情况下,都会显示一个 JDialog-window 缩小图像。 300 毫秒后,此 window 自动关闭(ImageDialog 和 CardPrinter 的构造函数中有一个计时器;这仅用于调试)。 在生产版本中,程序必须能够在 JDialog 中加载 30-40 张图像(一张一张)。用户键入一些文本,然后单击按钮以显示下一张图像。 在这两种情况下,我都使用 ImageIO.read() 加载图像。或者我使用 CMYKJPEGImage (http://www.randelshofer.ch/blog/2011/08/reading-cmyk-jpeg-images-with-java-imageio/) 但它也会产生异常。 在 Main 中有 myTimer,它不断地打开带有图像文件的 JDialog(listFilesRecurse() 应该通过设置变量 pathtofiles 用绝对路径调用)。

案例一) 使用覆盖布局管理器 (Main.java、CardPrinter.java、IDCardLayout.java) 在 JDialog 中加载图像。 为此,必须取消注释 Main.java 中从 'CardPrinter cp=null;' 到 'cp.dispose();' 的行。棘手的部分在 addIDCard()@CardPrinter 中。 这将是建立的布局。在这个 paintComponent()@IDCardLayout 被调用之后,如果对话框的内容-window 应该被实现。 貌似,加载的图片不会被破坏,内存变满了(在Window的Taskmanager中可以看到内存占用)。

案例二) 在没有布局管理器的 JDialog 中加载图像 (Main.java、ImageDialog.java)。 (取消注释 'mn.showDialog(mn.frame, file);' 行)。 工作完美。没有例外!

函数ImageIO.read()应该没问题,因为在2.情况下,没有错误(即使代码运行了20分钟)。 很奇怪,似乎在第一种情况下布局管理器阻止 GC 卸载未使用的图像...
1.case 中的问题应该在 CTR@CardPrinter 和 addIDCard()@CardPrinter 中。在 CTR 中,我使用 OverlayLayout。 2.case里面根本就没有Layoutmanager。
Java 版本是:Windows7 SP1 上的 1.7.0_80。
有人可以帮我吗?

提前谢谢你, 丹尼尔

The exception:

Exception in thread "AWT-EventQueue-0" java.lang.OutOfMemoryError: Java heap space at java.awt.image.DataBufferByte.(DataBufferByte.java:92) at java.awt.image.ComponentSampleModel.createDataBuffer(ComponentSampleModel.java:415) at java.awt.image.Raster.createWritableRaster(Raster.java:941) at javax.imageio.ImageTypeSpecifier.createBufferedImage(ImageTypeSpecifier.java:1073) at javax.imageio.ImageReader.getDestination(ImageReader.java:2896) at com.sun.imageio.plugins.jpeg.JPEGImageReader.readInternal(JPEGImageReader.java:1066) at com.sun.imageio.plugins.jpeg.JPEGImageReader.read(JPEGImageReader.java:1034) at javax.imageio.ImageIO.read(ImageIO.java:1448) at javax.imageio.ImageIO.read(ImageIO.java:1308) at test.IDCardLayout.loadPicture(IDCardLayout.java:128) at test.CardPrinter.addIDCard(CardPrinter.java:142) at test.CardPrinter.(CardPrinter.java:62) at test.CardPrinter.createDialog(CardPrinter.java:91) at test.Main.actionPerformed(Main.java:69) at javax.swing.Timer.fireActionPerformed(Timer.java:312) at javax.swing.Timer$DoPostEvent.run(Timer.java:244) at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:312) at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:745) at java.awt.EventQueue.access0(EventQueue.java:103) at java.awt.EventQueue.run(EventQueue.java:706) at java.awt.EventQueue.run(EventQueue.java:704) at java.security.AccessController.doPrivileged(Native Method) at java.security.ProtectionDomain.doIntersectionPrivilege(ProtectionDomain.java:76) at java.awt.EventQueue.dispatchEvent(EventQueue.java:715) at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:242) at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:161) at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:150) at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:146) at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:138) at java.awt.EventDispatchThread.run(EventDispatchThread.java:91)


package test;
/*Main.java*/


import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import java.util.Vector;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;


public class Main {
    JFrame frame;


    //list files recursively, using a filter (pattern). If pattern=*, all files will be listed.
    public static void listFilesRecurse(String fullpath, String pattern[], Vector<File> foundFiles) throws IOException {
        File dir = new File(fullpath);
        File list[] = dir.listFiles();
        for(File f: list) {
            if( !f.isDirectory() ) {
                for(String pt: pattern) {
                    if(pt.equals("*")) {
                        foundFiles.add(f);
                        break;
                    }
                    else if( f.getAbsolutePath().toUpperCase().endsWith(pt.toUpperCase()) ) {
                        foundFiles.add(f);
                        break;
                    }
                }
            } else {
                listFilesRecurse(f.getAbsolutePath(), pattern, foundFiles);
            }
        }
    }


public static void main(String[] args) {
    final Main mn = new Main();
    final String pathtofiles = "\path\to\files\";

    ActionListener task = new ActionListener() {
        public void actionPerformed(ActionEvent evt) {
            Vector<File> list = new Vector<File>();
            System.out.println("\nListing files ...");
            try {
                listFilesRecurse(pathtofiles, new String[]{"jpg", "png", "jpeg"}, list);
            } catch (IOException e) {
                e.printStackTrace();
            }
            for(int index=0; index<list.size(); index++) {
                String file = list.get(index).getAbsolutePath();
                System.out.println("\nLoading image - " + index + ": " + file);

                //mn.showDialog(mn.frame, file);


                CardPrinter cp=null;
                try {
                    cp = CardPrinter.createDialog(mn.frame, "Max",
                                                  "Muster", "1122", "12345678", "01/2018", file);
                } catch (Exception e) {
                    e.printStackTrace();
                    continue; //break;  //return;
                }
                cp.setVisible(true);
                cp.dispose();
            }
        }
    };
    final Timer myTimer = new Timer(100,  task);
    myTimer.setRepeats(true);


    JFrame frame = new JFrame("JFrame Example");
    mn.frame = frame;

    JPanel panel = new JPanel(); 
    panel.setLayout(new FlowLayout());

    JLabel label = new JLabel("This is a label!"); 
    JButton button = new JButton(); 

    button.setText("Press me");
    button.addActionListener( new ActionListener() {
        public void actionPerformed(ActionEvent actionEvent) {
            myTimer.start();
        }
    });

    panel.add(label);
    panel.add(button);
    frame.add(panel);
    frame.setSize(300, 300);
    frame.setLocationRelativeTo(null);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setVisible(true);
}


//opens the dialog that display an image (file).
public void showDialog(JFrame fr, String file) {
    ImageDialog imgd = new ImageDialog(fr, "Testdialog", file);
    imgd.setSize(300, 300);
    imgd.setModal(true);
    imgd.setLocation(250, 250);
    imgd.setVisible(true);
}

}

package test;
/*ImageDialog.java*/

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.Timer;

import org.monte.cmykdemo.CMYKJPEGImage;


public class ImageDialog extends JDialog {
    String file;


    public ImageDialog(JFrame frame, String title, String file) {
        super(frame, title);
        this.file = file;

        //######For debugging only. This will close the dialog window after 300 ms (by calling ImageDialog.this.setVisible(false);).
        ActionListener task = new ActionListener() {
            public void actionPerformed(ActionEvent evt) {
                ImageDialog.this.dispose();
            }
        };
        Timer cl = new Timer(300, task);
        cl.start();
        //#######
    }


    //renders the picture on screen.
    @Override
    public void paint(Graphics g) {
        Graphics2D g2d = (Graphics2D) g;
        setRenderingHints(g2d);

        BufferedImage img=null;
        try {
            img = loadPicture(this.file);
        } catch (IOException e) {
            e.printStackTrace();
        }

        g2d.setColor(Color.BLUE);
        g2d.drawRect(0, 0, this.getWidth(), this.getHeight());
        g2d.drawImage(img, 10, 10,  80,  80,  null, null);
    }


    void setRenderingHints(Graphics2D g2) {
        g2.addRenderingHints(new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY));
        g2.addRenderingHints(new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON));
        g2.addRenderingHints(new RenderingHints(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON));
    }

    public static BufferedImage loadPicture(String fullpath) throws IOException {
        System.out.println("\nloadPicture(): " + fullpath);
        //BufferedImage img = (BufferedImage) CMYKJPEGImage.loadImage( fullpath );
        BufferedImage img = (BufferedImage) ImageIO.read( new File(fullpath) );
        return img;
    }
}

package test;
/*CardPrinter.java*/

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.print.PageFormat;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.OverlayLayout;
import javax.swing.Timer;
import javax.swing.border.Border;
import javax.swing.border.LineBorder;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.BorderFactory;
import javax.swing.JButton;


public class CardPrinter extends JDialog /*implements Printable*/ {
  protected JPanel jp;
  protected JTextField tx;
  protected JLabel lblCounter;
  protected IDCardLayout idcard;
  protected String vorname, nachname, id, gueltig, bild;

  protected String chipid;


  protected CardPrinter(Window owner, String vorname, String nachname, String id, String chipid,
                        String gueltig, String bild /*, DataReceiver drv*/) throws IOException {
      super((Window)owner, "Assign_ChipID_DialogObject");
      this.bild = bild;
      //this.datareceiver = drv;
      setTitle("Assign ID");
      setModalityType(JDialog.DEFAULT_MODALITY_TYPE);
      setResizable(false);

      jp = new JPanel();
      jp.setLayout(null);
      jp.setOpaque(false);

      addIDCard();
      //setupButtons();

      //setSize(this.getPreferredSize().width, this.getPreferredSize().height);
      setSize(250, 250); //+++
      setLocation(300, 300); //+++

      //Overlay components: tx overlaps idcard
      JPanel cnt = new JPanel();
      cnt.setLayout(new OverlayLayout(cnt));
      cnt.add(jp);
      getContentPane().add(cnt);

      setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);


      //######For debugging only. This will close the dialog window after 300 ms (by calling CardPrinter.this.setVisible(false);).
      ActionListener task = new ActionListener() {
          public void actionPerformed(ActionEvent evt) {
              CardPrinter.this.setVisible(false);
          }
      };
      Timer closeWnd = new Timer(300, task);
      closeWnd.start();
      //######
  }


  //use this to create an instance of this class.
  public static CardPrinter createDialog(Window owner, String vorname,
                String nachname, String id, String chipid, String gueltig, String bild  /*, DataReceiver drv*/) throws IOException {
      return new CardPrinter(owner, vorname, nachname, id, chipid, gueltig, bild  /*, drv*/);
  }


  protected void addIDCard() throws IOException {
      int idcard_move_vert=15, idcard_move_hor=76;
      tx = new JTextField(this.chipid, 8);
      jp.add(tx);
      tx.setBounds(idcard_move_hor+130, idcard_move_vert+6, 70, 20);
      Border border = BorderFactory.createLineBorder(Color.RED);
      tx.setBorder(border);
      tx.requestFocus();

      lblCounter = new JLabel("");
      lblCounter.setBounds(idcard_move_hor+270, idcard_move_vert-6, 40, 30);
      jp.add(lblCounter);
      lblCounter.requestFocus();

      System.out.println("\naddIDCard() ...");

      BufferedImage picture = IDCardLayout.loadPicture(this.bild);
      idcard = new IDCardLayout(/*this.vorname, this.nachname, this.id,*/ picture, this.bild  /*, this.gueltig*/);

      idcard.setLayout(null);
      //idcard.setBounds(idcard_move_hor, idcard_move_vert, idcard.getPreferredSize().width, idcard.getPreferredSize().height);
      idcard.setBounds(idcard_move_hor, idcard_move_vert, 50, 60);
      idcard.setBorder( new LineBorder(Color.BLACK) );
      jp.add(idcard);

      //repaint();
  }
}

package test;
/*IDCardLayout.java*/

import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.font.FontRenderContext;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.JPanel;

import org.monte.cmykdemo.CMYKJPEGImage;


public class IDCardLayout extends JPanel {
    private BufferedImage bild;
    private String bildfile;


    public IDCardLayout(BufferedImage photo, String bildfile) {
        this.bild = photo;
        this.bildfile = bildfile;
    }


    /**
     * Render graphics to the screen.
     */
    protected void paintComponent(Graphics g) {
        Graphics2D g2d = (Graphics2D) g;
        System.out.println("\npaintComponent() ...");
        prepareGraphics(g2d);
    }


    public static BufferedImage loadPicture(String fullpath) throws IOException {
        //BufferedImage img = (BufferedImage) CMYKJPEGImage.loadImage( f.getAbsolutePath() );
        BufferedImage img = (BufferedImage) ImageIO.read( new File(fullpath) );

        return img;
    }


    //changing the rendering hints has no effect on Outofmemory-exception.
    protected void setRenderingHints(Graphics2D g2, boolean normalText) {
        if(true) {
            g2.addRenderingHints(new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY));
            g2.addRenderingHints(new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON));
            g2.addRenderingHints(new RenderingHints(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON));
        }
    }


    /**
     * Renders graphics to the screen.
     */
    protected void prepareGraphics(Graphics2D g2) {
        setRenderingHints(g2, true);

        double newHeight = 60;
        double scwidth = ( ((double)newHeight / (double)this.bild.getHeight()) ) * (double)this.bild.getWidth();
        System.out.println("\nprepareGraphics() ..." + this.bildfile);
        g2.drawImage(this.bild,  5,  5,
                    (int)scwidth,
                    (int)newHeight,
                    null, null);
    }
}

问题已解决。我不再使用 class OverlayLayout。布局看起来很不一样,但现在可以了。