tags:

views:

493

answers:

9

Hello, my Java written application consumes way too much memory.

How does program work : User selects a date from calendar (GUI) and application loads data into JTable component. Everytime data is loaded, new TableModel is created and set. No new JTable is created, just model.

What is the problem? : every new day selection from calendar and loading to JTable consumes about 2-3 MB of memory. On start app consumes cca 50-60 MB of RAM, after few "clicks" on calendar (like 20), application consumes full Heap Size (128MB). Application crashes, of course ...

What should I do? : I am pretty sure database querys are OK. I might somehow set bigger heap size (I googled, but that would be only solution for my computer, users wont do this) OR I should somehow remove old TableModel with DB data. But shouldn't this be Garbage collector's work? I am able to force it (System.gc()) but that doesn't help ...

Thank you for any advice!

EDIT : Code for Handling events of calendar (I deleted Javadoc, it is in my mother tongue)

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
    }
}

Using NetBeans profiler : Date taken: Sun Feb 28 14:25:16 CET 2010 File: C:...\private\profiler\java_pid4708.hprof File size: 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
+6  A: 

Have you run a profiler like YourKit against this ? I suspect it'll show some memory leak due to references being held when they should be released. Note that System.gc() is a hint to the JVM, and doesn't force a GC cycle.

Alternatively, your application may simply require more memory than the JVM is permitted to allocate. The JVM will only allocate up to a default maximum (dependent on your platform). Try increasing this via:

java -Xmx256m {classname}

etc. to see if this fixes the problem permanently. If it doesn't, then this points to a memory leak.

Brian Agnew
I am going to read about YourKit, thanx
Miso
Too bad it aint free :-(
Miso
Try using the free JVisualVM that is bundled in the bin directory of JDK_1.6.10
crowne
You can download an evaluation YourKit, which may be enough to diagnose your problem (15 days)
Brian Agnew
I used JVisualVM (good thing btw) and it says I only use 20-30 mb of heap, windows task manager shows 80-90 ...
Miso
@Miso: heap is not the only memory Java occupies. Also: that discrepancy doesn't matter for your problem: use the memory profiler.
Joachim Sauer
I did please take a look to SS I posted below
Miso
A: 

128m is not so much for java. Increase it to 512m at least. If problem will still exists, then we really have a memory leak

UPD: 128m is really too little for real java app, and i think that there can be not a memory leak, but a lack of memory.

splix
Ok I might have very stupid question, but I need to ask :If I increase to 512M, will I increase only on my JVM or on any computer where application will be executed?
Miso
Only on your machine. Write a start-up script that sets the max heap size appropriately for your users. They'll appreciate your effort to bundle it up for them.
duffymo
I might google the script (never written such thing myself), but dont you have something similar? :) Would save me lot of work ...
Miso
I just mean putting "java -Xmx 512m /* your classpath and class here */" in a .bat or .cmd file for Windows or .sh on *nix. Nothing fancy. It's just one line, the same thing you'd type in a command shell to start your app.
duffymo
doubling the max memory will get you a bit more time before the app crashes but you're still going to have a memory leak you need to fix.
Robin
Adding memory is not the solution to a memory leak.
Milan Ramaiya
Yes, it's not a solution, it's just adding a memory. I don't know any java app that can run using only 128m, and i think that there are not a memory leak, it's no enough memory for app.
splix
Whe I run with 512 megs its much better, but its very "brute" solution, if so ever :(
Miso
bgw, only after this you can look for "memory leaks", if it exists
splix
Memory leaks do exists in my app (sadly).They are some undeleted references to Swing components, but I am unable to found which are those yet
Miso
I've written many production apps in Java which worked fine under 128 megs. If you know what you're doing, 128 is fine. If you don't, well, you're only adding more memory to reduce the amount of time before restart. Don't blame a lack of ability on the programming language/VM.
Milan Ramaiya
A: 

A wild guess here, but as I see this a lot in C#, are your calendar/controls event handlers holding references to data that doesn't get cleaned up properly? Make sure you null out handles when you no longer need them as circular dependencies will lead to large leaks.

Blindy
Well I have DateSelectionListener for DateSelectionEventevery time date is changed, new TableMoel is created (Listener does this, and also loads data from DB)
Miso
Look very carefully at what goes on in those handlers, all of them. If ANY reference is held in a handler and the handler is held by the dialog box, that memory won't get freed. This is the downside of using GC languages, you can't just delete the pointer and see where the null pointer exception pops up.
Blindy
I added some code, please take a look maybe you'll notice some error
Miso
+2  A: 

Read Veijko Krunic's great paper How to Fix Memory Leaks in Java. He suggests a diagnostic path for similar problems.

Yuval F
A: 

You don't need dates down to millisecond accuracy every time. It looks to me like having the same day of week is sufficient.

Personally, I'd figure out a way to prepopulate this calendar and cache it. No need to re-create it each time. It's worth a try. If you reuse it each and every day, why re-create it each time? Have a Timer repopulate it at midnight each day. Make it read-only and allow all users to share it.

Don't need a "new" Calendar each time, either. I'd do it this way:

Calendar cal = Calendar.getInstance();

Let the factory dole it out.

I'd also recommend looking at a library like JODA for time. It's certain to be more efficient than what you're doing here.

UPDATE: Maybe this can help you ferret out your memory leak. At least it's a checklist of where to start looking.

duffymo
you mean like create class atribute and declare it in constructor?
Miso
See edit above.
duffymo
+2  A: 

Clearly some amount of objects are getting created for each 'click' on the calendar, These objects aren't getting garbage collected, hence the increasing memory usage and eventual crash. Without actually running the code, from looking at your code sample I'd say a likely culprit is the anonymous inner class created here:

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

The new DateSelectionListener you create will have a reference to this (the WorkerMonthViewHandler), I can't see exactly how that would cause an issue without knowing more about how initMonthView is used, but I've found refactoring anonymous inner classes created as listeners on swing objects has helped identify and ultimately solve a number of memory leaks in the past. The listeners will exist as long as the swing object they are listening to exists, so will hang around even after you create a new WorkerMonthViewHandler assuming the original swing JTable is still the same.

If you want some further reading on this try this, http://www.javalobby.org/java/forums/t19468.html.

Hope this helps.

Robin
If I understand correctly, I should create only one instance of DateSelectionListener (f.e. in constructor) ?You are probably right though, I am 99% sure its Swing
Miso
factor out the listener to it's own class, this will make it clearer what it is holding references to and possibly enlighten you as to the source of your memory leak. Just only creating one but then constantly adding it to components may not solve your problem. Once you've refactored it into it's own class you may still need to make additional changes but hopefully it'll be clearer what's going on.
Robin
@Miso, not really... the real issue is that if you add listeners over and over then you also need to remove them again. The single biggest source of memory leaks I've found in Swing-based applications is code forgetting to clean up its listeners. Anonymous inner classes exacerbate the problem because they hold references to their outer instances.
PSpeed
...which you've discovered from profiling. :) Still useful to remember.
PSpeed
yep:)but still I actually dont remove them, I just dont create so many of them ...
Miso
A: 

This entirely sounds like a memory leak with your Swing components. There's some component which is being instantiated multiple times, and attached to something else (usually as a listener) so it can't be garbage collected, since there's still a valid reference to it. As someone else pointed, any profiler will help you find the source.

Take a heap snapshot at the beginning of the application. Then after you hit the button about ten times, take another heap snapshot and do a diff. There should be a set of objects which you know shouldn't still be in memory, but are. Then you can find out what's holding the reference to it, and fix those.

Milan Ramaiya
So I've been investigatingTake a look at pic I was clicking like a mad man on cells which have CellEditor set as my custom SpinnerEditor (in other words, double click on cell to handle JSpinner) ....Look at used/allocated heap, it in fact only rises ....http://i49.tinypic.com/160wx0g.jpg
Miso
Right, which is even more evidence that some objects just aren't being released. (This has happened to me in the past when I had a reference to a dialog in a HashMap from outside, so even when I disposed the dialog, there was still a reference to it and Java wouldn't GC it.) Use something like YourKit or JProfiler and do a diff on two different heap dumps. (JProfiler will tell you what object are new in between heaps, so you can quickly ignore those instances that are part of the application rather than what you're doing.)
Milan Ramaiya
A: 

It's hard to tell from the code, but any chance you are continuously adding 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
        }
    }
Jim Rush
Nope, only first time JXmonthView is initialized (and also on RESET JButton), so not often :)
Miso
A: 

Gentelmen, thank you all for answers. I appreciate every single response.

I added my own to be more visible, cant really choose one correct.

So when you look more carefully to the code I posted in original question, you'll find these two lines

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

Result is, everytime NEW TableModel is created with its own Listener, as some of you noticed. So now I only reload data (not whole model) and use original Listener.

Take a look to the picture, now it consumes way too less RAM and GC actually works :) alt text

Miso