Env:
JDK: 1.8u112 oracle
JRE: 10.0.2
JVM max heap size: ~2GB.
OS: Windows 10
IDE: Netbeans 8.1
RAM: DDR4 8GB
Processor: 6700hq i7 intel
Context
A simple GUI that opens an image file (jpg/png) and magnifies it via user input.
Desc
A class extends JFrame. The frame's contentPane has a JButton,a JLabel & a JScrollPane. Clicking the button shows a 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). The label has a mouse wheel listener that causes zooming of image via Image.getScaledInstance
. At each zoom, magnifiaction (ratio of new image width(or height) to corresponding original's) and Runtime.totalMemory
is printed.
Problem
- 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. Expected should be around the order of 11.8*11.8*7.23KB
- Upon zooming out, the memory consumption doesn't reduce
- Why is the heap expanding so much(at around ~17 times mag, it reaches 2GB) in the first place? Are discarded
ImageIcon
objects(see code) not being gced? - How to make code viable for mag where mag * mag * originalImageSize(in bytes)<50% JVM max heap size?
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.
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: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:1862MB 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