2009-03-09 11 views
36

Estoy tomando algunas aplicaciones de un desarrollador anterior. Cuando ejecuto las aplicaciones a través de Eclipse, veo que el uso de la memoria y el tamaño del almacenamiento dinámico aumentan mucho. Tras una investigación más a fondo, veo que estaban creando un objeto una y otra vez en un bucle, así como otras cosas.¿Cuáles son algunas de las mejores prácticas de administración de memoria de Java?

Empecé a realizar una limpieza. Pero cuanto más pasaba, más preguntas tenía "¿Esto realmente hará algo?"

Por ejemplo, en lugar de declarar una variable fuera del ciclo mencionado anteriormente y simplemente establecer su valor en el ciclo ... crearon el objeto en el ciclo. Lo que quiero decir es:

for(int i=0; i < arrayOfStuff.size(); i++) { 
    String something = (String) arrayOfStuff.get(i); 
    ... 
} 

frente

String something = null; 
for(int i=0; i < arrayOfStuff.size(); i++) { 
    something = (String) arrayOfStuff.get(i); 
} 

Am I incorrecto decir que la curva inferior es mejor? Quizás estoy equivocado.

Además, ¿qué pasa después del segundo ciclo anterior, configuro "algo" de nuevo en nulo? ¿Eso borrará algo de memoria?

En cualquier caso, ¿cuáles son algunas de las mejores prácticas de administración de memoria que podría seguir que ayudarán a mantener mi uso de memoria bajo en mis aplicaciones?

Actualización:

aprecio gustos respuesta hasta ahora. Sin embargo, realmente no estaba preguntando acerca de los bucles anteriores (aunque según su consejo, volví al primer bucle). Estoy tratando de obtener algunas de las mejores prácticas que puedo vigilar. Algo en la línea de "cuando hayas terminado de usar una colección, despejala". Realmente necesito asegurarme de que estas aplicaciones no ocupen tanta memoria.

+0

Re la edición: Como tres personas han dicho hasta ahora, perfílalo! –

+0

Estoy buscando algunas prácticas específicas para usar, así que no es necesario que alguien lo haga. Prefiero desarrollarlo correctamente en primer lugar que codificar, espero que funcione, perfilarlo y hacer correcciones. – Ascalonian

+2

La "práctica específica" es escribir código bien estructurado sin realizar una optimización prematura y luego perfilarlo para descubrir qué necesita optimizarse. –

Respuesta

36

No intente engañar a la máquina virtual. El primer ciclo es la mejor práctica recomendada, tanto para el rendimiento como para la mantenibilidad. Volver a establecer la referencia en nulo después del ciclo no garantizará la liberación inmediata de la memoria. El GC hará su mejor trabajo cuando use el alcance mínimo posible.

Los libros que cubren estas cosas en detalle (desde la perspectiva del usuario) son Effective Java 2 y Implementation Patterns.

Si desea obtener más información sobre el rendimiento y los inicios de la VM, necesita ver las charlas o leer libros del Brian Goetz.

+0

" use el alcance mínimo posible "¿se considera una mejor práctica en general? – KillBill

+0

Creo que generalmente sí. Pero, por supuesto, debería haber excepciones a esa generalización. – cherouvim

9

Esos dos bucles son equivalentes, excepto por el alcance de something; ver this question para más detalles.

¿Mejores prácticas generales? Umm, veamos: no almacene grandes cantidades de datos en variables estáticas a menos que tenga una buena razón. Elimine los objetos grandes de las colecciones cuando haya terminado con ellos. Y, oh, sí, "medir, no adivinar". Use un generador de perfiles para ver dónde se está asignando la memoria.

+3

+1 únicamente para "Medir, no adivinar". –

5

Los dos bucles utilizarán básicamente la misma cantidad de memoria, cualquier diferencia sería insignificante. "Cadena de algo" solo crea una referencia a un objeto, no un objeto nuevo en sí mismo y, por lo tanto, cualquier memoria adicional utilizada es pequeña. Además, el compilador/combinado con JVM probablemente optimice el código generado de todos modos.

Para las prácticas de gestión de la memoria, realmente debe tratar de perfilar la memoria mejor para comprender dónde están realmente los cuellos de botella.Mire especialmente las referencias estáticas que apuntan a una gran parte de la memoria, ya que eso nunca se recopilará.

También puede consultar las referencias débiles y otras clases especializadas de gestión de memoria.

Por último, tenga en cuenta, que si una aplicación necesita memoria, puede haber una razón para ello ....

actualización La clave para la gestión de memoria es estructuras de datos, así como la cantidad de rendimiento que necesita/cuándo. La compensación suele ser entre la memoria y los ciclos de la CPU.

Por ejemplo, se puede ocupar una gran cantidad de memoria con el almacenamiento en caché, que está específicamente allí para mejorar el rendimiento ya que está tratando de evitar una operación costosa.

Por lo tanto, piense a través de sus estructuras de datos y asegúrese de no guardar cosas en la memoria por más tiempo del que debe. Si es una aplicación web, evite almacenar una gran cantidad de datos en la variable de sesión, evite tener referencias estáticas a enormes grupos de memoria, etc.

4

El primer ciclo es mejor. Debido

  • la variable algo va a quedar claro más rápido (teórico)
  • el programa es mejor leer.

Pero desde el punto de memoria esto es irrelevante.

Si tiene problemas de memoria, debe indicar dónde se consume.

+0

Lo siento, corrígeme si me equivoco, pero el primer ciclo asigna memoria para una nueva variable en cada ciclo. Si el ciclo se ejecuta 100 veces, ¿por qué es mejor crear 100 cadenas en lugar de simplemente anular el mismo y asignar memoria solo para 1 cadena (como en el segundo ciclo)? – Mapisto

+0

La memoria en la pila para todas las variables locales de un método se asigna en la entrada de método y no en el punto de declaración en el código. El tamaño de dicha variable es de 4 bytes. (posible 8 bytes en VM de 64 bit). El tamaño de la cadena se asigna en el montón y no en la pila. Esta asignación ocurre en la palabra clave "nueva". No hay diferencia en la asignación de las cadenas. – Horcrux7

8

No hay objetos creados en sus dos ejemplos de código. Simplemente establece una referencia de objeto a una cadena que ya está en la matrizOfStuff. Entonces, en la memoria no hay diferencia.

1

Bueno, el primer ciclo es en realidad mejor, porque el alcance de algo es más pequeño. En cuanto a la gestión de la memoria, no supone una gran diferencia.

La mayoría de los problemas de memoria de Java se producen cuando almacena objetos en una colección, pero se olvida de eliminarlos. De lo contrario, el GC hace que su trabajo sea bastante bueno.

1

El primer ejemplo está bien. No hay ninguna asignación de memoria allí, aparte de una asignación de variable de pila y desasignación cada vez que pasa el ciclo (muy barato y rápido).

La razón es que todo lo que se 'asigna' es una referencia, que es una variable de pila de 4 bytes (en la mayoría de los sistemas de 32 bits de todos modos). Una variable de pila se 'asigna' al agregar a una dirección de memoria que representa la parte superior de la pila, por lo que es muy rápido y económico.

Lo que hay que tener cuidado es de bucles como:

for (int i = 0; i < some_large_num; i++) 
{ 
    String something = new String(); 
    //do stuff with something 
} 

ya que es en realidad haciendo allocatiton memoria.

+0

Ni siquiera hay asignación en la pila para cada iteración. Estos se asignan en la tabla de variables locales del método (una vez) pero la variable sale automáticamente del alcance cuando termina el ciclo. –

+0

, sí, pero se crea un nuevo objeto String en cada iteración, que es más caro que una asignación de pila. – workmad3

5

La JVM es la mejor para liberar objetos efímeros. Intente no asignar objetos que no necesita. Pero no puede optimizar el uso de la memoria hasta que comprenda su carga de trabajo, la duración del objeto y el tamaño de los objetos. Un perfilador puede decirte esto.

Finalmente, la cosa n. ° 1 que debe evitar: nunca use Finalizer.Los finalizadores interfieren con la recolección de basura, ya que el objeto no puede liberarse, sino que debe ponerse en cola para su finalización, lo que puede ocurrir o no. Lo mejor es nunca usar finalizadores.

En cuanto al uso de la memoria que se está viendo en Eclipse, que no es necesariamente relevante. El GC hará su trabajo basado en la cantidad de memoria libre que hay. Si tiene mucha memoria libre, es posible que no vea un solo GC antes de que se cierre la aplicación. Si encuentra que su aplicación se está quedando sin memoria, solo un perfilador real puede indicarle dónde están las fugas o las ineficiencias.

+0

En los lenguajes de recolección de basura orientados a objetos, el rendimiento deficiente a menudo es causado por un gran número de objetos de larga vida, especialmente si hay muchas capas de composición. Por ejemplo, si creo un objeto de hoja de cálculo, con una matriz 2D de objetos de celda, donde cada objeto contiene un objeto Color, objeto Value y objeto Formatting, y cada objeto Formatting contiene ..... Bueno, se entiende la idea. El GC tardará mucho tiempo en tratar el gráfico de objetos. También las capas de indirección de memoria causan retraso (lo hace en Smalltalk, de todos modos. No estoy tan seguro acerca de Java). – ahoffer

1

Si ha hecho ya, te sugiero instalar el Eclipse Test & Performance Tools Platform (TPTP). Si desea volcar e inspeccionar el montón, consulte las herramientas SDK jmap and jhat. También vea Monitoring and Managing Java SE 6 Platform Applications.

+1

Como TPTP parece realmente muerto ahora (5 años después), vaya a jvisualvm que se envía con JDK y analice los registros de gc (http://www.tagtraum.com/gcviewer.html o https://github.com/chewiebug/GCViewer). –

4

En mi opinión, debe evitar micro-optimizaciones como estas. Cuestan muchos ciclos cerebrales, pero la mayoría de las veces tienen poco impacto.

Su aplicación probablemente tiene algunas estructuras de datos centrales. Esos son los que deberían estar preocupados. Por ejemplo, si los llena, predíquelos con una buena estimación del tamaño, para evitar el cambio de tamaño repetido de la estructura subyacente. Esto se aplica especialmente a StringBuffer, ArrayList, HashMap y similares. Diseña bien tu acceso a esas estructuras, para que no tengas que copiar mucho.

Utilice los algoritmos adecuados para acceder a las estructuras de datos. En el nivel más bajo, como el ciclo que mencionó, use Iterator s, o al menos evite llamar al .size() todo el tiempo. (Sí, está pidiendo la lista cada vez que se trata de su tamaño, que la mayoría de las veces no cambia). Por cierto, a menudo he visto un error similar con Map s. Las personas iteran sobre keySet() y get cada valor, en lugar de simplemente iterar sobre el entrySet() en primer lugar. El administrador de memoria le agradecerá los ciclos de CPU adicionales.

2

Como un cartel anterior sugiere, el uso de perfiles para medir la memoria (y/o CPU) de ciertas partes de su programa en lugar de tratar de adivinar. ¡Quizás te sorprendas con lo que encuentres!

También hay un beneficio adicional. Comprenderá más sobre su lenguaje de programación y su aplicación.

utilizo VisualVM para perfilar y lo recomiendo mucho. Viene con distribución jdk/jre.

0

"¿Es incorrecto decir que el ciclo inferior es mejor?", La respuesta es NO, no solo es mejor, en el mismo caso es necesario ... La definición de variable (no el contenido), se realiza en la memoria montón, y está limitado, en el primer ejemplo, cada bucle crear una instancia en esta memoria, y si el tamaño de "arrayOfStuff" es grande, puede ocurrir "Fuera de error de memoria: espacio de montón java" ....

0

Por lo que entiendo que estás viendo, el ciclo inferior no es mejor. La razón es que incluso si está intentando reutilizar una sola referencia (Ex-algo), el hecho es que el objeto (Ex-arrayOfStuff.get (i)) todavía se está haciendo referencia desde la Lista (arrayOfStuff). Para que los objetos sean elegibles para la recopilación, no se les debe hacer referencia desde ningún lugar. Si está seguro de la vida de la lista después de este punto, puede decidir eliminar/liberar los objetos de ella, dentro de un bucle separado.

La optimización que se puede hacer desde un punto de vista estático (es decir, sin modificación está sucediendo a la lista de cualquier otro hilo), es mejor evitar la invocación de tamaño() varias veces. Es decir, si no espera que el tamaño cambie, entonces ¿por qué calcularlo una y otra vez? después de todo, no es un array.length, es list.size().

Cuestiones relacionadas