Java занимает слишком много памяти - PullRequest
12 голосов
/ 28 февраля 2010

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

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

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

Что мне делать? : Я почти уверен, что запросы к базе данных в порядке. Я мог бы как-то установить больший размер кучи (я гуглил, но это было бы единственным решением для моего компьютера, пользователи этого не делают) ИЛИ мне как-то нужно удалить старую TableModel с данными БД. Но это не должно быть мусором коллекторская работа? Я могу заставить его (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: Дата съемки: вс 28 февраля 14:25:16 CET 2010 Файл: C: ... \ private \ profiler \ java_pid4708.hprof Размер файла: 72,2 МБ

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 ]

8 голосов
/ 28 февраля 2010

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

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

java -Xmx256m {classname}

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

2 голосов
/ 28 февраля 2010

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

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

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

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

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

2 голосов
/ 28 февраля 2010

Прочитайте большую статью Вейко Круника Как исправить утечки памяти в Java . Он предлагает диагностический путь для подобных проблем.

0 голосов
/ 28 февраля 2010

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

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

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

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

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

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

0 голосов
/ 28 февраля 2010

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

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

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

0 голосов
/ 28 февраля 2010

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

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

Также не нужен каждый раз "новый" Календарь. Я бы сделал это так:

Calendar cal = Calendar.getInstance();

Дайте фабрике покончить с этим.

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

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

0 голосов
/ 28 февраля 2010

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...