2010-02-28 20 views
12

mi aplicación Java escrita consume demasiada memoria.Java consume demasiada memoria

Cómo funciona el programa: El usuario selecciona una fecha del calendario (GUI) y la aplicación carga datos en el componente JTable. Cada vez que se cargan datos, se crea y configura nuevo TableModel. No se crea ninguna JTable nueva, solo modelo.

¿Cuál es el problema?: cada nueva selección del día desde el calendario y cargando a JTable consume aproximadamente 2-3 MB de memoria. En la aplicación de inicio consume cca 50-60 MB de RAM, después de unos pocos "clics" en el calendario (como 20), la aplicación consume un tamaño de montón completo (128 MB). La aplicación se bloquea, por supuesto ...

¿Qué debo hacer?: Estoy bastante seguro de que las consultas de bases de datos están bien. De alguna manera podría establecer un tamaño de almacenamiento dinámico más grande (busqué en Google, pero esa sería la única solución para mi computadora, los usuarios no lo harían) O debería de alguna manera eliminar el viejo TableModel con datos de base de datos. ¿Pero no debería ser este el trabajo del recolector de basura? Puedo forzarlo (System.gc()) pero eso no ayuda ...

¡Gracias por cualquier consejo!

EDIT: código para manejar eventos de calendario (He eliminado Javadoc, que está en mi lengua materna)

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

Uso de NetBeans Profiler: Fecha de la toma: Dom 28 14:25:16 CET febrero 2010 del archivo: C: tamaño ... \ privado \ perfiles \ java_pid4708.hprof del archivo: 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 
+1

Podemos ver el código? – Fernando

+1

Usted menciona un DB ... ¿puede decirnos más sobre lo que está haciendo, qué DB está utilizando, si está cerrando sus ResultSets, etc. ... parece ser un culpable mucho más probable. Además, ¿conserva alguna referencia a los viejos modelos? ¿Cuántos datos cargas en un modelo? ¿Su mesa tiene procesadores personalizados? – PSpeed

+0

Nota: en el pasado he intercambiado JTable TableModels que contenía muchos miles de filas ... y nunca incrementé el tamaño del montón de forma predeterminada. Entonces algo más está en juego aquí. – PSpeed

Respuesta

8

¿se ha quedado como un generador de perfiles YourKit contra esta ? Sospecho que mostrará alguna pérdida de memoria debido a las referencias que se llevan a cabo cuando deberían ser liberados. Tenga en cuenta que System.gc() es una sugerencia en la JVM, y no fuerza un ciclo de GC.

Como alternativa,, es posible que su aplicación requiera más memoria de la permitida por la JVM. La JVM solo asignará hasta un máximo predeterminado (dependiendo de su plataforma). Intente aumentar esto a través de:

java -Xmx256m {classname} 

etc. para ver si esto soluciona el problema de forma permanente. Si no es así, esto apunta a una pérdida de memoria.

+0

Voy a leer sobre YourKit, thanx – Miso

+0

Lástima que no es gratuito :-( – Miso

+4

Intente utilizar el JVisualVM gratuito que se incluye en el directorio bin de JDK_1.6.10 – crowne

0

Supongo que es un error, pero como veo esto mucho en C#, ¿sus controladores de eventos de calendario/controles tienen referencias a datos que no se limpian correctamente? Asegúrese de anular las asas cuando ya no las necesite, ya que las dependencias circulares provocarán grandes fugas.

+0

Bueno, tengo DateSelectionListener para DateSelectionEvent cada vez que se cambia la fecha, se crea el nuevo TableMoel (el oyente hace esto y también carga los datos de DB) – Miso

+0

Mire con atención lo que sucede en esos manejadores, todos ellos. Si CUALQUIER referencia se mantiene en un controlador y el manejador se mantiene en el cuadro de diálogo, esa memoria no se liberará. Esta es la desventaja de utilizar lenguajes de GC, no puede simplemente eliminar el puntero y ver dónde aparece la excepción del puntero nulo. – Blindy

+0

He añadido un código, por favor, eche un vistazo, tal vez notará algún error – Miso

0

No necesita fechas con una precisión de milisegundos en todo momento. Me parece que basta con tener el mismo día de la semana.

Personalmente, encontraría una manera de rellenar este calendario y guardarlo en caché. No es necesario volver a crearlo cada vez. Vale la pena intentarlo.Si lo reutilizas todos los días, ¿por qué volver a crearlo cada vez? Haga que un temporizador lo reponga a medianoche todos los días. Haga que sea de solo lectura y permita que todos los usuarios lo compartan.

No necesita un calendario "nuevo" cada vez, tampoco. Lo haría de esta manera:

Calendar cal = Calendar.getInstance(); 

Deje que la fábrica lo distribuya.

También recomendaría buscar una biblioteca como JODA por tiempo. Es seguro que será más eficiente que lo que estás haciendo aquí.

ACTUALIZACIÓN: Quizás this puede ayudarle a descubrir su pérdida de memoria. Al menos es una lista de verificación de dónde empezar a buscar.

+0

¿Quiere decir crear el atributo de clase y declararlo en el constructor? – Miso

+0

Ver la edición anterior. – duffymo

2

Claramente, se crea una cantidad de objetos para cada 'clic' en el calendario. Estos objetos no reciben basura recolectada, de ahí el uso cada vez mayor de la memoria y el bloqueo eventual. Sin ejecutar el código, de mirar a su ejemplo de código que diría una causa más probable es la clase interna anónima creada aquí:

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

El nuevo DateSelectionListener que cree tendrá una referencia a esto (el WorkerMonthViewHandler), No veo exactamente cómo eso podría causar un problema sin saber más sobre cómo se usa initMonthView, pero he encontrado que la refacturación de clases internas anónimas creadas como oyentes en objetos colgantes ha ayudado a identificar y finalmente resolver una serie de pérdidas de memoria en el pasado . Los oyentes existirán siempre que exista el objeto swing que están escuchando, por lo que seguirán existiendo incluso después de crear un nuevo WorkerMonthViewHandler suponiendo que el swing JTable original sigue siendo el mismo.

Si quiere leer más sobre esto, intente esto, http://www.javalobby.org/java/forums/t19468.html.

Espero que esto ayude.

+0

Si entiendo correctamente, debería crear solo una instancia de DateSelectionListener (por ejemplo, en constructor)? Probablemente estés en lo cierto, estoy 99% seguro de que su Swing – Miso

+0

excluye al oyente de su propia clase, esto aclarará a qué se refiere y posiblemente te ilumine en cuanto a la fuente de tu pérdida de memoria. Solo crear una, pero agregarla constantemente a los componentes puede no resolver su problema. Una vez que lo hayas refactorizado en su propia clase, es posible que aún tengas que hacer cambios adicionales, pero espero que sea más claro lo que está pasando. – Robin

+1

@Miso, no realmente ... el verdadero problema es que si agrega oyentes una y otra vez, también debe eliminarlos nuevamente. La fuente más grande de pérdidas de memoria que he encontrado en las aplicaciones basadas en Swing es olvidar el código para limpiar a sus oyentes. Las clases internas anónimas agravan el problema porque contienen referencias a sus instancias externas. – PSpeed

0

Esto suena por completo como una pérdida de memoria con sus componentes Swing. Hay algún componente que se está instanciando varias veces, y se adjunta a otra cosa (por lo general como un oyente) por lo que no se puede recoger basura, ya que todavía hay una referencia válida a la misma. Como señaló otra persona, cualquier generador de perfiles lo ayudará a encontrar la fuente.

Tome una instantánea del montón al comienzo de la aplicación. Luego, después de presionar el botón unas diez veces, tome otra instantánea de montón y haga una diferencia. Debería haber un conjunto de objetos que usted sabe que aún no deberían estar en la memoria, pero sí lo son. Luego puede averiguar qué contiene la referencia y corregirlos.

+0

Así que he estado investigando Echando un vistazo a pic Estaba haciendo clic como un loco en las celdas que tienen CellEditor establecer como mi SpinnerEditor personalizado (en otras palabras, haga doble clic en la celda para manejar JSpinner) .... Observe el montón usado/asignado, de hecho, solo se levanta .... http://i49.tinypic.com /160wx0g.jpg – Miso

+1

Correcto, lo que es aún más evidencia de que algunos objetos simplemente no se están liberando. (Esto me ha sucedido en el pasado cuando tenía una referencia a un diálogo en un HashMap desde el exterior, por lo que incluso cuando deseché el diálogo, todavía había una referencia y Java no lo GC). Use algo como YourKit o JProfiler y hacer una diferencia en dos vuelcos de montón diferentes. (JProfiler le dirá qué objeto son nuevos entre montones, por lo que puede ignorar rápidamente las instancias que son parte de la aplicación en lugar de lo que está haciendo). –

0

Es difícil de decir desde el código, pero ¿hay alguna posibilidad de que agregue continuamente 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

No, solo la primera vez que se inicializa JXmonthView (y también en RESET JButton), por lo tanto no muy a menudo :) – Miso

-1

Gentelmen, gracias por todas las respuestas. Agradezco cada respuesta.

He añadido el mío para ser más visible, realmente no puedo elegir uno correcto.

Así que cuando se mira con más cuidado al código que he publicado en la pregunta original, encontrará estas dos líneas

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

resultado es, cada vez NUEVO TableModel se crea con su propio oyente, como algunos de ustedes notado. Así que ahora solo vuelvo a cargar datos (no todo el modelo) y uso el Oyente original.

Echa un vistazo a la foto, ahora que consume demasiado menos RAM y GC realmente funciona :) alt text http://img694.imageshack.us/img694/1604/gcworks.jpg

Cuestiones relacionadas