简体   繁体   English

Java中图像缩放的内存问题

[英]Memory issue with image zooming in java

Env: ENV:

JDK: 1.8u112 oracle JDK:1.8u112甲骨文

JRE: 10.0.2 JRE:10.0.2

JVM max heap size: ~2GB. JVM最大堆大小:〜2GB。

OS: Windows 10 作业系统:Windows 10

IDE: Netbeans 8.1 IDE:Netbeans 8.1

RAM: DDR4 8GB 内存:DDR4 8GB

Processor: 6700hq i7 intel 处理器:6700hq i7 intel

Context 上下文

A simple GUI that opens an image file (jpg/png) and magnifies it via user input. 一个简单的GUI,可打开图像文件(jpg / png)并通过用户输入将其放大。

Desc 说明

A class extends JFrame. 一个类扩展了JFrame。 The frame's contentPane has a JButton,a JLabel & a JScrollPane. 框架的contentPane具有JButton,JLabel和JScrollPane。 Clicking the button shows a JFileChooser. 单击该按钮将显示一个JFileChooser。 The label is inside the scrollpane. 标签在滚动窗格内。 Selecting a file opens it in the label(open image files only for the purposes of this question-jpg/png tested upon). 选择一个文件将在标签中将其打开(仅出于测试此问题jpg / png的目的打开图像文件)。 The label has a mouse wheel listener that causes zooming of image via Image.getScaledInstance . 标签具有鼠标滚轮侦听器,可通过Image.getScaledInstance缩放图像。 At each zoom, magnifiaction (ratio of new image width(or height) to corresponding original's) and Runtime.totalMemory is printed. 每次缩放时,都会打印放大倍率(新图像宽度(或高度)与相应原始图像的比率)和Runtime.totalMemory

Problem 问题

  1. Upon zooming into the image, too much memory seems to being consumed by the code. 放大图像后,代码似乎占用了太多内存。 The task manager shows 1708 MB memory usage at 11.8 times magnification for a 7.23KB png image. 对于7.23KB png图像,任务管理器以11.8倍的放大倍数显示了1708 MB的内存使用情况。 Expected should be around the order of 11.8*11.8*7.23KB 预期应该在11.8 * 11.8 * 7.23KB左右
  2. Upon zooming out, the memory consumption doesn't reduce 缩小后,内存消耗不会减少
  3. Why is the heap expanding so much(at around ~17 times mag, it reaches 2GB) in the first place? 为什么堆首先扩展得如此之大(约为mag的17倍,达到2GB)? Are discarded ImageIcon objects(see code) not being gced? 是否没有给丢弃的ImageIcon对象(请参见代码)?
  4. How to make code viable for mag where mag * mag * originalImageSize(in bytes)<50% JVM max heap size? 如何使mag * mag * originalImageSize(以字节为单位)<50%JVM最大堆大小的mag可行代码?

Code

import java.awt.Dimension;
import java.awt.Image;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFileChooser;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;

public class gui extends javax.swing.JFrame {

Image image;
Dimension size;
private double mag = 1;
Runtime runtime = Runtime.getRuntime();

public gui() {
    initComponents();

}

private void zoom() {

    int[] newSize = {(int) (size.width * mag), (int) (size.height * mag)};

    if (newSize[0] > 0 && newSize[1] > 0) {
        label.setIcon(new ImageIcon(image.getScaledInstance(newSize[0], newSize[1], Image.SCALE_DEFAULT)));
    }

    System.out.println("mag:" + (int) (mag * 100) + "% mem:" + runtime.totalMemory() / 1024 / 1024 + "MB");

}

private void loadImage(File imgFile) throws IOException {

    String path = imgFile.getPath().toLowerCase();
    if (path.endsWith("gif")) {
        ImageIcon icon = new ImageIcon(path);

        image = icon.getImage();

        label.setIcon(icon);

    } else {
        image = ImageIO.read(imgFile);
        ImageIcon icon = new ImageIcon(image);
        label.setIcon(icon);
    }

    size = new Dimension(image.getWidth(null), image.getHeight(null));

}

@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">                          
private void initComponents() {

    dialog = new javax.swing.JFileChooser();
    jScrollPane1 = new javax.swing.JScrollPane();
    label = new javax.swing.JLabel();
    button = new javax.swing.JButton();

    dialog.setCurrentDirectory(new java.io.File("D:\\"));
        dialog.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                dialogActionPerformed(evt);
            }
        });

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
        setTitle("Image Viewer");

        label.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
        label.setVerticalAlignment(javax.swing.SwingConstants.TOP);
        label.addMouseWheelListener(new java.awt.event.MouseWheelListener() {
            public void mouseWheelMoved(java.awt.event.MouseWheelEvent evt) {
                labelMouseWheelMoved(evt);
            }
        });
        jScrollPane1.setViewportView(label);

        button.setText("open");
        button.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                buttonActionPerformed(evt);
            }
        });

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 689, Short.MAX_VALUE)
                    .addGroup(layout.createSequentialGroup()
                        .addComponent(button)
                        .addGap(0, 0, Short.MAX_VALUE)))
                .addContainerGap())
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(button)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 430, Short.MAX_VALUE)
                .addContainerGap())
        );

        pack();
    }// </editor-fold>                        

private void dialogActionPerformed(java.awt.event.ActionEvent evt) {                                       
    // TODO add your handling code here:

    if (evt.getActionCommand().equals(JFileChooser.APPROVE_SELECTION)) {

        try {
            File file = dialog.getSelectedFile();

            loadImage(file);

            setTitle(file.getPath());
        } catch (IOException ex) {
            ex.printStackTrace();
        }

    }

}                                      

private void labelMouseWheelMoved(java.awt.event.MouseWheelEvent evt) {                                      

    if (image != null) {
        int amt = -evt.getWheelRotation();
        double newMag = mag + amt * 0.1;

        if (newMag > 0) {
            mag = newMag;
            zoom();

        }

    }


}                                     

private void buttonActionPerformed(java.awt.event.ActionEvent evt) {                                       
    // TODO add your handling code here:
    dialog.showOpenDialog(this);
}                                      

public static void main(String args[]) throws Exception {

    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());

    SwingUtilities.invokeLater(new Runnable() {
        public void run() {

            new gui().setVisible(true);

        }
    });
}

// Variables declaration - do not modify                     
private javax.swing.JButton button;
private javax.swing.JFileChooser dialog;
private javax.swing.JScrollPane jScrollPane1;
private javax.swing.JLabel label;
// End of variables declaration                   
}

Test file 测试文件

Any jpg or png image should do besides test file . 测试文件外,任何jpg或png图像都应执行。

Output from the test file 测试文件的输出

mag:110% mem:123MB mag:120% mem:123MB mag:130% mem:123MB mag:140% mem:123MB mag:150% mem:123MB mag:160% mem:123MB mag:150% mem:123MB mag:160% mem:123MB mag:170% mem:123MB mag:180% mem:155MB mag:190% mem:155MB mag:200% mem:155MB mag:210% mem:155MB mag:220% mem:155MB mag:230% mem:157MB mag:240% mem:157MB mag:250% mem:157MB mag:260% mem:157MB mag:270% mem:253MB mag:280% mem:253MB mag:290% mem:253MB mag:300% mem:253MB mag:310% mem:253MB mag:320% mem:256MB mag:330% mem:256MB mag:340% mem:256MB mag:350% mem:256MB mag:360% mem:256MB mag:370% mem:393MB mag:380% mem:393MB mag:390% mem:393MB mag:400% mem:393MB mag:410% mem:393MB mag:420% mem:393MB mag:430% mem:466MB mag:440% mem:466MB mag:450% mem:466MB mag:460% mem:466MB mag:470% mem:466MB mag:480% mem:466MB mag:489% mem:541MB mag:499% mem:541MB mag:509% mem:541MB mag:519% mem:541MB mag:529% mem:541MB mag:539% mem:641MB mag:549% mem:641MB mag:559% mem:641MB mag:569% mem:641MB mag:579% mem:641MB mag:589% mem:825MB mag:599% mem:825MB mag:609% mem 大小:110%内存:123MB大小:120%内存:123MB大小:130%内存:123MB大小:140%内存:123MB大小:150%内存:123MB大小:160%内存:123MB大小:150%内存:123MB大小:160%mem:123MB mag:170%mem:123MB mag:180%mem:155MB mag:190%mem:155MB mag:200%mem:155MB mag:210%mem:155MB mag:220%mem:155MB mag: 230%mem:157MB mag:240%mem:157MB mag:250%mem:157MB mag:260%mem:157MB mag:270%mem:253MB mag:280%mem:253MB mag:290%mem:253MB mag:300 %mem:253MB mag:310%mem:253MB mag:320%mem:256MB mag:330%mem:256MB mag:340%mem:256MB mag:350%mem:256MB mag:360%mem:256MB mag:370% mem:393MB mag:380%mem:393MB mag:390%mem:393MB mag:400%mem:393MB mag:410%mem:393MB mag:420%mem:393MB mag:430%mem:466MB mag:440%mem :466MB mag:450%mem:466MB mag:460%mem:466MB mag:470%mem:466MB mag:480%mem:466MB mag:489%mem:541MB mag:499%mem:541MB mag:509%mem: 541MB大小:519%大小:541MB大小:529%大小:541MB大小:539%大小:641MB大小:549%大小:641MB大小:559%大小:641MB大小:569%大小:641MB大小:579%大小:641MB mag:589%mem:825MB mag:599%mem:825MB mag:609%mem :825MB mag:619% mem:825MB mag:609% mem:825MB mag:619% mem:825MB mag:629% mem:892MB mag:639% mem:892MB mag:649% mem:892MB mag:659% mem:892MB mag:669% mem:892MB mag:679% mem:892MB mag:689% mem:881MB mag:699% mem:881MB mag:709% mem:881MB mag:719% mem:881MB mag:729% mem:1029MB mag:739% mem:1029MB mag:749% mem:1029MB mag:759% mem:1029MB mag:769% mem:1104MB mag:779% mem:1104MB mag:789% mem:1104MB mag:799% mem:1104MB mag:809% mem:1075MB mag:819% mem:1075MB mag:829% mem:1075MB mag:839% mem:1182MB mag:849% mem:1182MB mag:859% mem:1182MB mag:869% mem:1289MB mag:879% mem:1289MB mag:889% mem:1542MB mag:899% mem:1542MB mag:909% mem:1542MB mag:919% mem:1569MB mag:929% mem:1569MB mag:939% mem:1569MB mag:949% mem:1480MB mag:959% mem:1480MB mag:969% mem:1548MB mag:979% mem:1548MB mag:989% mem:1655MB mag:999% mem:1655MB mag:1009% mem:1707MB mag:1019% mem:1707MB mag:1029% mem:1802MB mag:1039% mem:1850MB mag:1049% mem:1850MB mag:1059% mem:1871MB mag:1069% mem:1871MB mag:1079% mem:1801MB mag:1089% mem:1862 :825MB mag:619%mem:825MB mag:609%mem:825MB mag:619%mem:825MB mag:629%mem:892MB mag:639%mem:892MB mag:649%mem:892MB mag:659%mem: 892MB mag:669%mem:892MB mag:679%mem:892MB mag:689%mem:881MB mag:699%mem:881MB mag:709%mem:881MB mag:719%mem:881MB mag:729%mem:1029MB mag:739%mem:1029MB mag:749%mem:1029MB mag:759%mem:1029MB mag:769%mem:1104MB mag:779%mem:1104MB mag:789%mem:1104MB mag:799%mem:1104MB mag :809%mem:1075MB mag:819%mem:1075MB mag:829%mem:1075MB mag:839%mem:1182MB mag:849%mem:1182MB mag:859%mem:1182MB mag:869%mem:1289MB mag: 879%mem:1289MB mag:889%mem:1542MB mag:899%mem:1542MB mag:909%mem:1542MB mag:919%mem:1569MB mag:929%mem:1569MB mag:939%mem:1569MB mag:949 %mem:1480MB mag:959%mem:1480MB mag:969%mem:1548MB mag:979%mem:1548MB mag:989%mem:1655MB mag:999%mem:1655MB mag:1009%mem:1707MB mag:1019% mem:1707MB mag:1029%mem:1802MB mag:1039%mem:1850MB mag:1049%mem:1850MB mag:1059%mem:1871MB mag:1069%mem:1871MB mag:1079%mem:1801MB mag:1089%mem :1862 MB mag:1099% mem:1862MB mag:1109% mem:1815MB mag:1119% mem:1822MB mag:1129% mem:1758MB mag:1139% mem:1774MB mag:1149% mem:1711MB mag:1159% mem:1734MB mag:1169% mem:1676MB mag:1179% mem:1708MB mag:1189% mem:1654MB MB大小:1099%大小:1862MB大小:1109%大小:1815MB大小:1119%大小:1822MB大小:1129%大小:1758MB大小:1139%大小:1774MB大小:1149%大小:1711MB大小:1159%大小:1734MB大小:1169%内存:1676MB大小:1179%内存:1708MB大小 :1189%内存:1654MB

Are discarded ImageIcon objects(see code) not being gced? 是否没有给丢弃的ImageIcon对象(请参见代码)?

How they could be GC'ed without the GC running? 如果不运行GC,如何对它们进行GC处理?

Why should the GC run when the memory suffices? 当内存足够时,为什么要运行GC?

That's it. 而已。 The GC runs when needed and there isn't much to win by keeping the memory usage lower than necessary. GC在需要时运行,并且通过将内存使用率保持在低于必要水平的优势,并没有什么好处。

For efficiency reasons, the GC is actually a "survivor collector": It only deals with surviving objects and what's left behind is free memory. 出于效率方面的考虑,GC实际上是一个“幸存者收集器”:它仅处理幸存的对象,而剩下的是可用内存。 Therefore it makes sense to run it ALAP as most objects die young. 因此,当大多数对象都年轻时,将其运行为ALAP是有意义的。


Expected should be around the order of 11.8*11.8*7.23KB 预期应该在11.8 * 11.8 * 7.23KB左右

No, a Java process is free to use all the memory you gave it. 不,Java进程可以自由使用您为其分配的所有内存。

Upon zooming out, the memory consumption doesn't reduce 缩小后,内存消耗不会减少

Yes, as there's no need for the GC to run. 是的,因为无需运行GC。

Why is the heap expanding so much(at around ~17 times mag, it reaches 2GB) 为什么堆扩展如此之大(约为mag的17倍,达到2GB)

The images at all intermediate sizes are unreachable, but not yet collected. 所有中间尺寸的图像均无法访问,但尚未收集。

How to make code viable for mag where mag * mag * originalImageSize(in bytes)<50% JVM max heap size? 如何使mag * mag * originalImageSize(以字节为单位)<50%JVM最大堆大小的mag可行代码?

You can't. 你不能 When the memory will be needed by the Java process, then it gets reclaimed. 当Java进程需要内存时,它将被回收。


I was lying a bit. 我在撒谎。 You can call System.gc manually and it'll probably help. 您可以手动调用System.gc ,它可能会有所帮助。 But don't do it. 但是不要这样做。 While this answers the last question, it solves no real problem. 虽然这回答了最后一个问题,但没有解决任何实际问题。 If you want to keep the memory usage low, then give Java less memory using -Xmx1000M or alike. 如果要保持较低的内存使用量,请使用-Xmx1000M或类似方式为Java减少内存。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM