13

I have a small piece of code that takes a screenshot of my desktop every five minutes. However I'm a little confused by the amount of memory it takes up - often it will creep up to 200mb of RAM, which I'm sure is excessive... Can anyone tell me a) sensible ways to reduce the memory footprint or b) why it's going up at all?

/**
 * Code modified from code given in http://whileonefork.blogspot.co.uk/2011/02/java-multi-monitor-screenshots.html following a SE question at  
 * http://stackoverflow.com/questions/10042086/screen-capture-in-java-not-capturing-whole-screen and then modified by a code review at http://codereview.stackexchange.com/questions/10783/java-screengrab
 */
package com.tmc.personal;

import java.awt.AWTException;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;

import javax.imageio.ImageIO;

class ScreenCapture {

    static int minsBetweenScreenshots = 5;

    public static void main(String args[]) {
        int indexOfPicture = 1000;// should be only used for naming file...
        while (true) {
            takeScreenshot("ScreenCapture" + indexOfPicture++);
            try {
                TimeUnit.MINUTES.sleep(minsBetweenScreenshots);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    //from http://www.coderanch.com/t/409980/java/java/append-file-timestamp
    private  final static String getDateTime()
    {
        DateFormat df = new SimpleDateFormat("yyyy-MM-dd_hh:mm:ss");
        df.setTimeZone(TimeZone.getTimeZone("PST"));
        return df.format(new Date());
    }

    public static void takeScreenshot(String filename) {
        Rectangle allScreenBounds = getAllScreenBounds();
        Robot robot;
        try {
            robot = new Robot();
            BufferedImage screenShot = robot.createScreenCapture(allScreenBounds);
            ImageIO.write(screenShot, "jpg", new File(filename + getDateTime()+ ".jpg"));
        } catch (AWTException e) {
            System.err.println("Something went wrong starting the robot");
            e.printStackTrace();
        } catch (IOException e) {
            System.err.println("Something went wrong writing files");
            e.printStackTrace();
        }
    }

    /**
     * Okay so all we have to do here is find the screen with the lowest x, the
     * screen with the lowest y, the screen with the higtest value of X+ width
     * and the screen with the highest value of Y+height
     * 
     * @return A rectangle that covers the all screens that might be nearby...
     */
    private static Rectangle getAllScreenBounds() {
        Rectangle allScreenBounds = new Rectangle();
        GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
        GraphicsDevice[] screens = ge.getScreenDevices();

        int farx = 0;
        int fary = 0;
        for (GraphicsDevice screen : screens) {
            Rectangle screenBounds = screen.getDefaultConfiguration().getBounds();
            // finding the one corner
            if (allScreenBounds.x > screenBounds.x) {
                allScreenBounds.x = screenBounds.x;
            }
            if (allScreenBounds.y > screenBounds.y) {
                allScreenBounds.y = screenBounds.y;
            }
            // finding the other corner
            if (farx < (screenBounds.x + screenBounds.width)) {
                farx = screenBounds.x + screenBounds.width;
            }
            if (fary < (screenBounds.y + screenBounds.height)) {
                fary = screenBounds.y + screenBounds.height;
            }
            allScreenBounds.width = farx - allScreenBounds.x;
            allScreenBounds.height = fary - allScreenBounds.y;
        }
        return allScreenBounds;
    }
}
Joe
  • 4,367
  • 7
  • 33
  • 52
  • This is when you use a profiler. – Jeroen Vannevel Dec 06 '13 at 17:47
  • 1
    This is Java. A program that runs "forever" will eventually fill up the GC heap, before GC is triggered and everything's collected. You can adjust the max heap size if you feel it's taking too much system resource. – Hot Licks Dec 06 '13 at 17:47
  • 1
    Try System.gc(); before invoking sleep. It's a shitty hack, but will work :) – Amit Sharma Dec 06 '13 at 17:49
  • 2
    Calling `gc()` is a code smell and should be avoided. Note in particular that a compliant implementation of that method is for the JVM to do nothing at all. – Matt McHenry Dec 06 '13 at 17:56
  • @MattMcHenry Well, for that matter, garbage collection itself is optional. – chrylis -cautiouslyoptimistic- Dec 06 '13 at 18:32
  • How much heap is it using after a Full GC? Any other number you look at is likely to be a tuning issue. 200 MB costs about $0.50 and its reusable so its not that much. – Peter Lawrey Dec 06 '13 at 18:40
  • @chrylis: sort of. The JVM spec (sec 2.5.3) says "Heap storage for objects is reclaimed by an automatic storage management system (known as a garbage collector); objects are never explicitly deallocated. The Java Virtual Machine assumes no particular type of automatic storage management system, and the storage management technique may be chosen according to the implementor's system requirements." http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-2.html#jvms-2.5.3 – Matt McHenry Dec 07 '13 at 01:52
  • after the call to `ImageIO.write(...)` you can call `screenShot.flush()`. that will free up image resources immediately (a single 4k screencapture will take up ~32mb of ram) – kritzikratzi Jul 11 '17 at 18:11
  • @AmitSharma While this might work, it's not required to work by the standard. The documentation says that `System.gc()` gives a hint, which may or may not be used, to run the garbage collector. – user904963 Feb 06 '22 at 16:06
  • @chrylis-cautiouslyoptimistic- The garbage collector must exist and run at certain times to meet the requirements in Java. `System.gc()`, in those requirements, is defined as giving a hint to run the garbage collector. It expressly notes the hint may be ignored and/or the garbage collector may not run. – user904963 Feb 06 '22 at 16:08

3 Answers3

18

The other answers are right that Java will use as much memory as it is allowed to, at which point it will garbage collect. To work around this, you can specify a smaller max heap size in the JVM settings. You do this with the -Xmx setting. For example, if you think you only need 32MB, run it as:

java -Xmx32M [your main class or jar here]

The heap of your program (the non-stack memory) will never take more than 32MB, but it will crash if it needs more than that at once (and that's where you'll need to profile). I don't see any obvious leaks in your program (assuming ImageIO doesn't require any cleanup), though, so I think you'll be fine.

Mike Wasson
  • 316
  • 1
  • 3
  • 2
    Setting a lower heap size is a great diagnostic tool to decide whether you really do have a memory leak. You can also use JVisualVM (included with the JDK) to see how much time your app is spending in garbage collection. As long as that remains minimal, you probably don't have a memory leak. – Matt McHenry Dec 06 '13 at 17:58
  • `ver take more than 32MB, but it will crash if it needs more than that at once` - OR it will crash if GC spends too much time garbage collecting: https://stackoverflow.com/questions/1393486/error-java-lang-outofmemoryerror-gc-overhead-limit-exceeded – jannis Oct 20 '21 at 13:06
2

JVM garbage collector will eventually clear your memory heap. For manually clearing that heap call Runtime.getRuntime().gc();, but I don't advise doing that for every 5 minutes.

expozed
  • 31
  • 1
0

For a modern computer, 200MB is not an excessive amount of memory. The JVM will let the heap grow for a while if you're creating and discarding lots of objects so that your program doesn't get bogged down with garbage collection. Let your program run for several hours and then check back if you think there's a problem.

chrylis -cautiouslyoptimistic-
  • 75,269
  • 21
  • 115
  • 152
  • 25
    "200MB is not an excessive amount of memory" Unless I have 30 of them running. I can running 30 Python scripts at a time. 30 Java processes consume gigs, even for toy programs. – Paul Draper Feb 14 '15 at 20:39
  • 14
    Now take 200MB for every microservice on the server in the cloud at $ 5 on every 500MB. – deFreitas Jun 06 '16 at 04:03