【问题标题】:Why is this Java program taking up so much memory?为什么这个 Java 程序占用这么多内存?
【发布时间】:2013-12-06 17:44:54
【问题描述】:

我有一小段代码每五分钟对我的桌面进行一次屏幕截图。 但是,我对它占用的内存量感到有些困惑-通常它会爬到 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  
 * http://*.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;
    }
}

【问题讨论】:

  • 这是你使用分析器的时候。
  • 这是 Java。在触发 GC 并收集所有内容之前,“永远”运行的程序最终会填满 GC 堆。如果您觉得占用过多系统资源,可以调整最大堆大小。
  • 试试 System.gc();在调用睡眠之前。这是一个糟糕的黑客,但会工作:)
  • 调用gc() 是一种代码异味,应该避免。请特别注意,该方法的兼容实现是让 JVM 什么都不做。
  • @MattMcHenry 好吧,就此而言,垃圾收集本身是可选的。

标签: java memory


【解决方案1】:

其他答案是正确的,Java 将使用尽可能多的内存,此时它将进行垃圾收集。要解决此问题,您可以在 JVM 设置中指定较小的最大堆大小。您可以使用 -Xmx 设置来执行此操作。例如,如果你认为你只需要 32MB,那么运行它:

java -Xmx32M [your main class or jar here]

你的程序堆(非堆栈内存)永远不会超过 32MB,但如果它一次需要超过 32MB,它会崩溃(这就是你需要分析的地方)。不过,我没有在您的程序中看到任何明显的泄漏(假设 ImageIO 不需要任何清理),所以我认为您会没事的。

【讨论】:

  • 设置较小的堆大小是一个很好的诊断工具,可以确定您是否确实存在内存泄漏。您还可以使用 JVisualVM(包含在 JDK 中)来查看您的应用程序在垃圾收集中花费了多少时间。只要这仍然是最小的,你可能没有内存泄漏。
  • ver take more than 32MB, but it will crash if it needs more than that at once - 如果 GC 花费太多时间进行垃圾收集,它会崩溃:*.com/questions/1393486/…
【解决方案2】:

JVM 垃圾收集器最终会清除你的内存堆。用于手动清除该堆调用 Runtime.getRuntime().gc();,但我不建议每 5 分钟执行一次。

【讨论】:

    【解决方案3】:

    对于现代计算机,200MB 的内存并不算多。如果您要创建和丢弃大量对象,JVM 会让堆增长一段时间,这样您的程序就不会陷入垃圾收集的困境。让您的程序运行几个小时,然后检查是否有问题。

    【讨论】:

    • "200MB 不是过多的内存" 除非我有 30 个正在运行。我一次可以运行 30 个 Python 脚本。 30 个 Java 进程消耗 gigs,即使是玩具程序也是如此。
    • 现在云服务器上的每个微服务需要 200MB,每 500MB 需要 5 美元。