Java抛出OfMemory,即使(我认为)我已经将引用设置为Null

Java抛出OfMemory,即使(我认为)我已经将引用设置为Null,java,out-of-memory,Java,Out Of Memory,我有一个应用程序,它可以获取大量不同的图像,从中生成新图像,然后保存这些图像以用于制作视频。这些图像都是PNG的,视频长达几分钟,因此该程序需要大量内存,每33.33毫秒的视频播放时间播放一幅图像。当我处理一个视频时,一切正常。我甚至可以处理几个视频,一切都很好。但是,如果我尝试处理1+n个视频,最终会出现outofmemory错误 让我困惑的是错误是如何发生的。以下是发生错误的程序部分: ComposeVideoController cvc = new ComposeVideo

我有一个应用程序,它可以获取大量不同的图像,从中生成新图像,然后保存这些图像以用于制作视频。这些图像都是PNG的,视频长达几分钟,因此该程序需要大量内存,每33.33毫秒的视频播放时间播放一幅图像。当我处理一个视频时,一切正常。我甚至可以处理几个视频,一切都很好。但是,如果我尝试处理1+n个视频,最终会出现outofmemory错误

让我困惑的是错误是如何发生的。以下是发生错误的程序部分:

        ComposeVideoController cvc = new ComposeVideoController();          
        boolean made = cvc.setXmlUrl(sourcePath, saveDir, fileId);
        cvc = null;
更准确地说,错误发生在ComposeVideoController引用的一个框架构造类中。ComposeVideoController的作用域是一个void方法,如果要制作更多视频,该方法将递归运行。我已经检查了ComposeVideoController引用的所有对象,它是构建视频的库的入口点,并确保它们也都设置为null

如果任何单个视频未导致outofmemory错误,并且在制作任何给定视频后,ComposeVideoController超出范围并设置为空,如何在ComposeVideoController中获取outofmemory错误

完整的递归如下所示。我有一个方法可以检查队列中是否有新消息,消息是通过套接字发送的,如果有,它会调用处理视频的方法。如果不是,则递归结束:

private void processQueue() {
    if(makingVideo) 
        return;
    MakeVideoObject mvo = queue.remove(0);      
    makingVideo = true;

    String[] convertArr = mvo.getConvertArrayCommand();
    String sourcePath = convertArr[1];
    String fileId = convertArr[2] + ".mp4";
    String saveDir = convertArr[3] + System.getProperty("file.separator");
    try {
        ComposeVideoController cvc = new ComposeVideoController();          
        boolean made = cvc.setXmlUrl(sourcePath, saveDir, fileId);
        cvc = null;
        if(made) {              
            cleanDir(mvo);
        }
    }
    catch(Exception e) {
        e.printStackTrace();
    }
}

/**
 * Moves all the assets off to a storage directory where we can be 
 * able to recover the video assets if something goes wrong during 
 * video creation.
 * 
 * @param mvo
 */
private void cleanDir(MakeVideoObject mvo) {
    String[] convertArr = mvo.getConvertArrayCommand();
    String sourceDir = convertArr[1];
    String saveDir = convertArr[3] + System.getProperty("file.separator");
    String fileId = convertArr[2];
    sourceDir = sourceDir.substring(0, sourceDir.lastIndexOf(System.getProperty("file.separator")));
    try {
        File f = new File(sourceDir);
        File[] files = f.listFiles();
        for(File file : files) {
            if(file.getName().indexOf(fileId) != -1) {
                file.renameTo(new File(saveDir + file.getName()));
            }
        }
        makingVideo = false;
        mvo = null;
        if(queue.size() > 0) {
            processQueue();
        }           
    }
    catch(Exception e) {
        e.printStackTrace();
    }
}

[编辑以显示程序的更多内容]

使用-Xmx增加内存空间。但同时,我建议您直接调用System.gc,告诉您从哪里获得OutOfMemoryException。。。如果将其他对象置零,这将有很大帮助。如果以递归方式执行非平凡代码,无论是这类代码还是经典堆栈溢出(以先发生的为准),都会发生类似的情况。递归非常占用资源,应该不惜一切代价避免它。简单地用迭代算法交换递归将使您的错误消失,很可能是因为我最终找到了答案,所以我正在发布一个答案,在关闭的情况下,这有助于任何其他人在将来尝试诊断内存泄漏


在我的分析过程中,我可以看到内存中保存了一些对象,但这对我来说毫无意义,因为它们被设置为null。在再次查看其中一个对象后,我注意到我已将其声明为静态。因为它是静态的,所以它也有静态成员,其中一个是ConcurrentHashMap。。。因此,这些映射添加了一些内容,由于对象是静态的,因此对象及其成员永远不会被取消引用。关于为什么我几乎从不将对象声明为静态对象的另一个教训。

尝试使用-Xmx标志。您的一个视频似乎大于默认的最大堆大小。无法执行此操作。我在Win32机器上的最大内存。超过1280,JVM无法启动。如果这些都在64位机器上运行,我可以添加更多内存。我的机器有32GB的内存。但我的客户不喜欢这样。Windows 32位系统在我运行的每个测试中将JVM限制为1280 1281,JVM不会启动。您的系统设置是。。。不知怎么坏了。在windows 32位计算机上,分配的内存当然可能远远超过1281M。事实上,在Win32机器上最多可达3.2GB。我在许多Win 32机器上运行过它,其-Xmx高于1280,但出现错误,无法启动JVM。我无法控制我客户的机器。但是,我确实可以控制开始标志,所以我必须按照他们的机器可以接受的方式进行操作。jar作为CLI启动。这些机器都有4到6 GB的RAM。@HowardRoark如果您无法控制客户端,并且如果png图像的一个视频帧计数x平均大小大于堆,则无法将整个视频保存在内存中。你必须在构建视频时将其流式传输到文件中。这并不能解决任何问题:我不能使用Xmx来增加内存空间。这必须在各种系统上运行,包括WIN32位平台,我的Xmx和Xms标志已经达到1280M。再有,JVM就不能在32位机器上启动了。无论如何,它也帮不了什么忙。充其量,调用垃圾收集会推迟您的问题。哦,永远不要让Xms和Xmx的值相同,Xms必须比Xmx小得多,否则你将击败任何聪明的VMI内存管理。设置相同的值,这样GC就不必经常运行,并且知道无论如何我都必须至少有半个gig。这个答案也解释了为什么:我会尝试改变我的设计。我仍然很困惑为什么我会有这个问题。不管是递归还是否,GC文档说它将运行并回收任何设置为null的对象的所有内存,然后再抛出OfMemory。那么,为什么递归会起作用呢?谢谢你的反馈。我会给你一个建议
你无法预测GC何时会发生——它可能会准时发生,但也可能在你预期的很久之后发生。永远不要对垃圾收集做任何假设——如果你遇到内存问题,你将不得不重新考虑你的策略。例如,垃圾收集器可能会等待系统资源可用,或者它可能正忙于其他任务,或者它只是有一个意外的收集计划等等…递归…,应该不惜一切代价避免它。这是一个防守的极端位置。世界上有很多生产代码成功地使用了递归。如果递归的深度是有界的,如果您知道这个界,并且如果堆栈配置得足够大,那么就不会有问题。如果在不知道递归调用实际将使用多少堆栈的情况下这样做,那么是的,这是危险的。我花了很多时间使用Java等同步语言。我还花了很多时间使用异步语言,比如ActionScript。我所知道的用异步语言生成工作的、非平凡的软件的唯一方法是使用递归和事件。如果您尝试在这些语言中使用迭代,您的软件将失败。不过,这一点是正确的,在同步语言中,可能应该避免递归。在我的例子中,递归没有引起问题,但是静态类和成员引起了问题。既没有真正的异步语言,也没有真正的同步语言——如果你真的相信任何算法都只能递归实现。。。好你确实缺乏很多知识。但这没问题;我们都从某个地方开始,但请不要把你的经历说成是硬事实。别说了,伙计们,这太荒谬了。这又给了我一个教训,为什么我几乎从不声明对象为静态的,这是一个错误的结论。也许吧。我不是说我从来没有标记过一个静态类。我将它们用于不改变状态的对象。但是,例如,当测试中存在静态类时,我遇到了其他问题,因此我通常使用设计模式,不将对象标记为静态。软件应该是开放的扩展和封闭的修改。。。我还继承了一些项目,其中所有内容都标记为静态,这使得软件很难扩展。因此,我避免使用它们,但我当然明白它们有时是有用的。