为什么这个Java程序占用了如此多的内存?

13

我有一小段代码,它每五分钟截取一次我的桌面截图。然而,我有点困惑它占用的内存量 - 经常会达到200MB的RAM,我确定这是过度的... 有人可以告诉我 a) 合理地减少内存占用或 b) 为什么它 总是 在上涨吗?

/**
 * Code modified from code given in http://whileonefork.blogspot.co.uk/2011/02/java-multi-monitor-screenshots.html following a SE question at  
 * https://dev59.com/_mLVa4cB1Zd3GeqPv2bD 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;
    }
}

这是使用分析器的时候。 - Jeroen Vannevel
1
这是Java。一个永远运行的程序最终会填满GC堆,在GC被触发并收集所有内容之前。如果您觉得它占用了太多系统资源,可以调整最大堆大小。 - Hot Licks
1
在调用 sleep 之前尝试 System.gc()。这是一个糟糕的hack,但会起作用 :) - Amit Sharma
2
调用 gc() 是一种代码异味,应该避免使用。特别要注意的是,该方法的合规实现是让 JVM 什么也不做。 - Matt McHenry
完整的GC后,它使用了多少堆?您查看的任何其他数字都可能是一个调优问题。200 MB大约花费$0.50,而且它是可重复使用的,所以并不算太多。 - Peter Lawrey
显示剩余5条评论
3个回答

18
其他回答都是正确的,Java会使用允许使用的所有内存,直到进行垃圾回收。为了解决这个问题,你可以在JVM设置中指定较小的最大堆大小。你可以使用-Xmx参数来完成这个设置。例如,如果你认为只需要32MB,可以这样运行它:

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]

你程序的堆(非栈内存)不会超过32MB,但如果一次需要超过这个大小,它将崩溃(这时你需要进行性能分析)。我没有看到你的程序中有任何明显的泄漏(假设ImageIO不需要任何清理),所以我认为你应该没问题。


2
设置一个较低的堆大小是一个很好的诊断工具,用来确定您是否真的存在内存泄漏。您还可以使用JDK附带的JVisualVM来查看您的应用程序在垃圾收集方面花费了多少时间。只要保持最小限度,您可能就没有内存泄漏。 - Matt McHenry
如果一次性需要超过32MB的内存,程序将会崩溃。或者,如果垃圾回收器花费太多时间进行垃圾回收,也会导致程序崩溃。参考链接:https://dev59.com/anM_5IYBdhLWcg3wZSTX - jannis

2

JVM 垃圾回收器最终会清除您的内存堆。如果要手动清除该堆,则调用 Runtime.getRuntime().gc();,但我不建议每 5 分钟都这样做。


0
对于现代计算机来说,200MB并不是过多的内存。如果您正在创建和丢弃大量对象,则JVM将允许堆增长一段时间,以使您的程序不会因垃圾收集而陷入困境。让您的程序运行几个小时,然后再回来检查是否存在问题。

25
"200MB不是过多的内存",除非我同时运行30个。我可以同时运行30个Python脚本。即使是玩具级别的Java程序,30个进程也会消耗几个GB的内存。 - Paul Draper
14
现在在云服务器上,每个微服务需要使用200MB的空间,每500MB的存储费用为5美元。 - deFreitas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接