Подтвердить что ты не робот

Java потребляет слишком много памяти

мое написанное Java приложение потребляет слишком много памяти.

Как работает программа.. Пользователь выбирает дату из календаря (GUI) и приложение загружает данные в компонент JTable. Каждый раз данные загружаются, создается и настраивается новый TableModel. Никакой новый JTable не создается, просто модель.

В чем проблема?: каждый новый день от календаря и загрузка до JTable потребляет около 2-3 МБ памяти. При запуске приложение потребляет около 50-60 МБ оперативной памяти, после нескольких "кликов" по ​​календарю (например, 20) приложение потребляет полный размер кучи (128 МБ). Сбой приложений, конечно...

Что мне делать?. Я уверен, что запросы к базе данных в порядке. Я мог бы как-то установить больший размер кучи (я googled, но это будет только решение для моего компьютера, пользователи этого не сделают) ИЛИ мне нужно как-то удалить старый TableModel с данными БД. Но разве это не должно быть сборщиком мусора? Я могу заставить его (System.gc()), но это не помогает...

Спасибо за любой совет!

EDIT: Код для обработки событий календаря (я удалил 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:Дата съемки: вс 28 фев 14:25:16 CET 2010   Файл: 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
4b9b3361

Ответ 1

Запустили ли вы профайлер, например YourKit? Я подозреваю, что это покажет некоторую утечку памяти из-за того, что ссылки хранятся, когда они должны быть выпущены. Обратите внимание, что System.gc() является подсказкой для JVM и не вызывает цикл GC.

В качестве альтернативы ваше приложение может просто потребовать больше памяти, чем JVM разрешено выделять. JVM будет выделять до максимального значения по умолчанию (в зависимости от вашей платформы). Попробуйте увеличить это с помощью:

java -Xmx256m {classname}

и т.д.. чтобы устранить эту проблему навсегда. Если это не так, это указывает на утечку памяти.

Ответ 3

Очевидно, что некоторое количество объектов создается для каждого 'click' в календаре. Эти объекты не собирают мусор, следовательно, увеличиваются использование памяти и возможный сбой. Без фактического запуска кода, глядя на образец кода, я бы сказал, что вероятным виновником является анонимный внутренний класс, созданный здесь:

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

Новый создатель DateSelectionListener будет иметь ссылку на это (WorkerMonthViewHandler), я не вижу точно, как это может вызвать проблему, не зная больше о том, как используется initMonthView, но я нашел реорганизацию анонимных внутренних классов, созданных поскольку слушатели на качающихся объектах помогли идентифицировать и в конечном итоге решить ряд утечек памяти в прошлом. Слушатели будут существовать до тех пор, пока объект качания, который они прослушивают, существует, так что он будет зависать даже после того, как вы создадите новый WorkerMonthViewHandler, предполагая, что исходный Swing JTable все тот же.

Если вы хотите продолжить чтение, попробуйте это, http://www.javalobby.org/java/forums/t19468.html.

Надеюсь, что это поможет.

Ответ 4

Дикая угадайте здесь, но поскольку я вижу это много в С#, являются ли ваши обработчики событий календаря/элементов управления ссылками на данные, которые не очищаются должным образом? Удостоверьтесь, что вы удаляете ручки, когда вам больше не нужны, поскольку круговые зависимости приводят к большим утечкам.

Ответ 5

Вам не нужны даты с точностью до миллисекунды каждый раз. Мне кажется, что того же дня недели достаточно.

Лично я бы определил способ предварительного заполнения этого календаря и кэширования его. Не нужно воссоздавать его каждый раз. Это стоит попробовать. Если вы повторно используете его каждый день, зачем его воссоздавать каждый раз? Каждый день таймер переполняет его в полночь. Сделайте его доступным только для чтения и разрешите всем пользователям делиться им.

Не требуется также "новый" календарь каждый раз. Я бы сделал это следующим образом:

Calendar cal = Calendar.getInstance();

Пусть factory сделает это.

Я бы также рекомендовал посмотреть на библиотеку, такую ​​как JODA, на время. Это определенно более эффективно, чем то, что вы здесь делаете.

ОБНОВЛЕНИЕ: Возможно, this может помочь вам избавиться от утечки памяти. По крайней мере, это контрольный список того, с чего начать искать.

Ответ 6

Это похоже на утечку памяти с компонентами Swing. Там есть какой-то компонент, который создается несколько раз и привязан к чему-то другому (обычно как к слушателю), поэтому он не может быть собранным мусором, поскольку по-прежнему существует действительная ссылка на него. Как указал кто-то другой, любой профилировщик поможет вам найти источник.

Сделайте снимок кучи в начале приложения. Затем, когда вы нажмете кнопку примерно десять раз, сделайте еще один снимок кучи и сделайте разницу. Должен быть набор объектов, которые, как вы знаете, все еще не должны быть в памяти, но есть. Затем вы можете узнать, что содержит ссылку на него, и исправить их.

Ответ 7

Трудно сказать из кода, но любой шанс, что вы постоянно добавляете 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
        }
    }

Ответ 8

Gentelmen, спасибо всем за ответы. Я ценю каждый ответ.

Я добавил свой, чтобы быть более заметным, не могу действительно выбрать один правильный.

Поэтому, если вы посмотрите более внимательно на код, который я разместил в оригинальном вопросе, вы обнаружите эти две строки

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

В результате каждый раз, когда NEW TableModel создается с собственным слушателем, как некоторые из вас заметили. Так что теперь я перезагружаю только данные (не всю модель) и использую оригинальный Listener.

Посмотрите на картинку, теперь она потребляет слишком много ОЗУ и GC фактически работает :) alt text