Java消耗太多内存

时间:2010-02-28 12:01:53

标签: java heap

我的Java编写的应用程序消耗太多内存。

程序如何工作:用户从日历(GUI)中选择日期,应用程序将数据加载到JTable组件中。每次加载数据时,都会创建并设置新的TableModel。没有创建新的JTable,只是模型。

问题是什么?:从日历和加载到JTable的每个新的一天选择消耗大约 2-3 MB的内存。在启动应用程序时消耗cca 50-60 MB的RAM,在日历上几次“点击”(如20),应用程序消耗完整的堆大小(128MB)。应用程序崩溃,当然......

我该怎么办?:我非常确定数据库查询是否正常。我可能以某种方式设置更大的堆大小(我googled,但这只是我的计算机的解决方案,用户不会这样做)或者我应该以某种方式删除旧的TableModel与DB数据。但不应该这是垃圾收集器的工作?我能够强制它(System.gc()),但这没有帮助......

感谢您的任何建议!

编辑:处理日历事件的代码(我删除了Javadoc,用我的母语)

package timesheet.handlers;

import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import org.jdesktop.swingx.JXMonthView;
import org.jdesktop.swingx.event.DateSelectionEvent;
import org.jdesktop.swingx.event.DateSelectionListener;
import timesheet.database.WorkerOperations;
import timesheet.frames.WorkerFrame;
import timesheet.logictrier.*;


public class WorkerMonthViewHandler {
    private JXMonthView monthView;
    private WorkerFrame workerFrame;
    private WorkerOperations wops;
    private Date[] week = new Date[5];
    private WorkerTasksTableHandler wtth;

    public WorkerMonthViewHandler(WorkerFrame workerFrame) {
        this.workerFrame = workerFrame;
        this.monthView = workerFrame.getWorkerMonthView();
        wops = workerFrame.getWorkerOperations(); // for DB usage
    }

    public void initMonthView() {
        List<Task> tasks = wops.getWorkerTasks(workerFrame.getWorker()); // db select
        for (Task task : tasks) {
            if (!monthView.getSelection().contains(task.getPlannedStart())) { 
                monthView.addFlaggedDates(task.getPlannedStart());
                monthView.addFlaggedDates(task.gePlannedEnd()); // not really important
            }
        }
        monthView.setSelectionDate(new Date());
        monthView.getSelectionModel().addDateSelectionListener(new DateSelectionListener() {
            public void valueChanged(DateSelectionEvent dse) {
                Date d = monthView.getSelectionDate();
                for (int i=0; i<week.length; i++) {
                    if (d.equals(week[i])) {     
                        return;
                    }
                }
                Calendar cal = new GregorianCalendar();
                cal.setTime(d);
                long dayMs = 24 * 60 * 60 * 1000;
                switch (cal.get(Calendar.DAY_OF_WEEK)) {
                    case(Calendar.MONDAY) : {
                        week[0] = new Date(cal.getTimeInMillis());
                        week[1] = new Date(cal.getTimeInMillis()+dayMs);
                        week[2] = new Date(cal.getTimeInMillis()+2*dayMs);
                        week[3] = new Date(cal.getTimeInMillis()+3*dayMs);
                        week[4] = new Date(cal.getTimeInMillis()+4*dayMs);
                    } break;
                    case (Calendar.TUESDAY) : {
                        week[0] = new Date(cal.getTimeInMillis()-dayMs);
                        week[1] = new Date(cal.getTimeInMillis());
                        week[2] = new Date(cal.getTimeInMillis()+1*dayMs);
                        week[3] = new Date(cal.getTimeInMillis()+2*dayMs);
                        week[4] = new Date(cal.getTimeInMillis()+3*dayMs);
                    } break;
                    case (Calendar.WEDNESDAY) : {
                        week[0] = new Date(cal.getTimeInMillis()-2*dayMs);
                        week[1] = new Date(cal.getTimeInMillis()-dayMs);
                        week[2] = new Date(cal.getTimeInMillis());
                        week[3] = new Date(cal.getTimeInMillis()+1*dayMs);
                        week[4] = new Date(cal.getTimeInMillis()+2*dayMs);
                    } break;
                    case (Calendar.THURSDAY) : {
                        week[0] = new Date(cal.getTimeInMillis()-3*dayMs);
                        week[1] = new Date(cal.getTimeInMillis()-2*dayMs);
                        week[2] = new Date(cal.getTimeInMillis()-1*dayMs);
                        week[3] = new Date(cal.getTimeInMillis());
                        week[4] = new Date(cal.getTimeInMillis()+1*dayMs);
                    } break;
                    case (Calendar.FRIDAY) : {
                        week[0] = new Date(cal.getTimeInMillis()-4*dayMs);
                        week[1] = new Date(cal.getTimeInMillis()-3*dayMs);
                        week[2] = new Date(cal.getTimeInMillis()-2*dayMs);
                        week[3] = new Date(cal.getTimeInMillis()-dayMs);
                        week[4] = new Date(cal.getTimeInMillis());
                    } break;
                    case (Calendar.SATURDAY) : {
                        week[0] = new Date(cal.getTimeInMillis()-5*dayMs);
                        week[1] = new Date(cal.getTimeInMillis()-4*dayMs);
                        week[2] = new Date(cal.getTimeInMillis()-3*dayMs);
                        week[3] = new Date(cal.getTimeInMillis()-2*dayMs);
                        week[4] = new Date(cal.getTimeInMillis()-dayMs);
                    } break;
                    case (Calendar.SUNDAY) : {
                        week[0] = new Date(cal.getTimeInMillis()-6*dayMs);
                        week[1] = new Date(cal.getTimeInMillis()-5*dayMs);
                        week[2] = new Date(cal.getTimeInMillis()-4*dayMs);
                        week[3] = new Date(cal.getTimeInMillis()-3*dayMs);
                        week[4] = new Date(cal.getTimeInMillis()-2*dayMs);
                    } break;
                }
                wtth = new WorkerTasksTableHandler(workerFrame,week);
                wtth.createTable(); // sets model on JTable
            }
        });
    }

    public void reportTask() {
        wtth.reportTasks(); // simple DB insert
    }
}

使用NetBeans探查器:   截止日期:2010年2月28日星期二14:25:16     文件:C:... \ private \ profiler \ java_pid4708.hprof     文件大小:72,2 MB

Total bytes: 62 323 264
Total classes: 3 304
Total instances: 1 344 586
Classloaders: 18
GC roots: 2 860
Number of objects pending for finalization: 0

8 个答案:

答案 0 :(得分:8)

您是否针对此运行了YourKit这样的分析器?我怀疑它会显示一些内存泄漏,因为它们应该被释放时引用。请注意,System.gc()是JVM的提示,并且不会强制执行GC循环。

或者,您的应用程序可能只需要比允许JVM分配更多的内存。 JVM最多只能分配一个默认的最大值(取决于您的平台)。尝试通过以下方式增加:

java -Xmx256m {classname}

等。看看这是否能永久修复问题。如果没有,则指向内存泄漏。

答案 1 :(得分:2)

阅读Veijko Krunic的伟大论文How to Fix Memory Leaks in Java。他提出了类似问题的诊断路径。

答案 2 :(得分:2)

显然,日历上的每次“点击”都会创建一些对象。这些对象不会被垃圾收集,因此内存使用量增加,最终崩溃。在没有实际运行代码的情况下,通过查看代码示例,我会说可能的罪魁祸首是在这里创建的匿名内部类:

monthView.getSelectionModel().addDateSelectionListener(new DateSelectionListener() {
  ...
}

您创建的新DateSelectionListener将具有对此的引用(WorkerMonthViewHandler),我无法确切地知道如何在不知道如何使用initMonthView的情况下导致问题,但我发现重构的匿名内部类已创建因为摇摆物体上的听众帮助识别并最终解决了过去的一些内存泄漏问题。只要他们正在侦听的swing对象存在,侦听器就会存在,所以即使在创建一个新的WorkerMonthViewHandler之后,假设原始的swing JTable仍然相同,它也会一直存在。

如果您想进一步阅读此内容,请尝试http://www.javalobby.org/java/forums/t19468.html

希望这有帮助。

答案 3 :(得分:0)

这里有一个疯狂的猜测,但正如我在C#中看到的那样,你的日历/控件事件处理程序是否包含对未正确清理的数据的引用?确保在不再需要句柄时将其置空,因为循环依赖会导致大量泄漏。

答案 4 :(得分:0)

每次都不需要日期精确到毫秒级。在我看来,就像在一周的同一天就足够了。

就个人而言,我想办法预先填充这个日历并缓存它。无需每次都重新创建它。值得一试。如果您每天都重复使用它,为什么每次都重新创建它?让计时器在每天午夜重新填充。将其设为只读,并允许所有用户共享。

每次都不需要“新”日历。我这样做:

Calendar cal = Calendar.getInstance();

让工厂把它拿出来。

我还建议您暂时查看像JODA这样的图书馆。它肯定比你在这里做的更有效率。

更新:也许this可以帮助你发现内存泄漏。至少它是一个从哪里开始寻找的清单。

答案 5 :(得分:0)

这听起来像是Swing组件的内存泄漏。有一些组件被多次实例化,并附加到其他东西(通常作为一个监听器),所以它不能被垃圾收集,因为它仍然有一个有效的引用。正如其他人指出的那样,任何探查器都可以帮助您找到源。

在应用程序的开头拍摄堆快照。然后在按下按钮大约十次之后,再拍摄另一个堆快照并执行差异操作。应该有一组你知道不应该在内存中的对象,但是。然后你可以找出持有它的引用,并修复它们。

答案 6 :(得分:0)

从代码中很难说,但是你有可能不断添加FlaggedDates吗?

public void initMonthView() {
    List<Task> tasks = wops.getWorkerTasks(workerFrame.getWorker()); // db select
    for (Task task : tasks) {
        if (!monthView.getSelection().contains(task.getPlannedStart())) { 
            monthView.addFlaggedDates(task.getPlannedStart());
            monthView.addFlaggedDates(task.gePlannedEnd()); // not really important
        }
    }

答案 7 :(得分:-1)

Gentelmen,谢谢大家的回答。 我感谢每一个回复

我添加了自己的内容以便更加明显,不能选择一个正确的。

因此,当您仔细查看我在原始问题中发布的代码时,您会发现这两行

        wtth = new WorkerTasksTableHandler(workerFrame,week);
        wtth.createTable(); // sets model on JTable

结果是,每次使用自己的Listener创建NEW TableModel时,就像有些人注意到的那样。 所以现在我只重新加载数据(不是整个模型)并使用原始的Listener。

看看图片,现在消耗的内存太少RAM和GC实际上工作:) alt text http://img694.imageshack.us/img694/1604/gcworks.jpg