Después de responder una pregunta sobre cómo forzar objetos libres en Java (el chico estaba limpiando un HashMap de 1.5GB) System.gc()
, me dijeron que era una mala práctica llamar System.gc()
manualmente, pero los comentarios no fueron del todo convincentes. Además, nadie parecía atreverse a votar a favor ni a rechazar mi respuesta.
Allí me dijeron que es una mala práctica, pero también me dijeron que las ejecuciones del recolector de basura ya no detienen sistemáticamente al mundo, y que la JVM también podría usarlo efectivamente solo como una pista, así que estoy un poco en pérdida
Entiendo que la JVM generalmente sabe mejor que usted cuando necesita recuperar memoria. También entiendo que preocuparse por unos pocos kilobytes de datos es una tontería. También entiendo que incluso megabytes de datos no es lo que era hace unos años. Pero aún así, ¿1,5 gigabytes? Y sabes que hay como 1,5 GB de datos en memoria; No es como un tiro en la oscuridad. ¿Es System.gc()
sistemáticamente malo o hay algún punto en el que se vuelve correcto?
Entonces la pregunta es en realidad doble:
- ¿Por qué es o no es una mala práctica llamar
System.gc()
? ¿Es realmente solo una pista para la JVM bajo ciertas implementaciones, o es siempre un ciclo de recolección completo? ¿Realmente hay implementaciones de recolectores de basura que pueden hacer su trabajo sin detener el mundo? Por favor, arroje algo de luz sobre las diversas afirmaciones que la gente ha hecho en los comentarios a mi respuesta . - ¿Dónde está el umbral? ¿ Nunca es una buena idea llamar
System.gc()
, o hay momentos en que es aceptable? Si es así, ¿cuáles son esos tiempos?
fuente
Respuestas:
La razón por la que todos siempre dicen que se debe evitar
System.gc()
es que es un buen indicador de código fundamentalmente roto . Cualquier código que dependa de él para su corrección está ciertamente roto; cualquiera que dependa de él para el rendimiento probablemente esté roto.No sabes bajo qué tipo de recolector de basura estás ejecutando. Ciertamente, hay algunos que no "detienen el mundo" como usted afirma, pero algunas JVM no son tan inteligentes o por varias razones (¿tal vez están en un teléfono?) No lo hacen. No sabes lo que va a hacer.
Además, no se garantiza que haga nada. La JVM puede ignorar por completo su solicitud.
La combinación de "no sabes lo que hará", "no sabes si incluso ayudará" y "no deberías llamarlo de todos modos" es la razón por la cual las personas son tan contundentes al decir que en general No deberías llamarlo. Creo que es un caso de "si necesita preguntar si debería usar esto, no debería"
EDITAR para abordar algunas preocupaciones del otro hilo:
Después de leer el hilo que vinculó, hay algunas cosas más que me gustaría señalar. Primero, alguien sugirió que las llamadas
gc()
pueden devolver la memoria al sistema. Eso ciertamente no es necesariamente cierto: el montón de Java en sí mismo crece independientemente de las asignaciones de Java.Como en, la JVM mantendrá la memoria (muchas decenas de megabytes) y crecerá el montón según sea necesario. No necesariamente devuelve esa memoria al sistema incluso cuando libera objetos Java; es perfectamente libre de conservar la memoria asignada para usar en futuras asignaciones de Java.
Para mostrar que es posible que
System.gc()
no haga nada, vea:http://bugs.sun.com/view_bug.do?bug_id=6668279
y en particular que hay una opción -XX: DisableExplicitGC VM.
fuente
System.gc()
es útil e incluso podría ser necesario. Por ejemplo, en aplicaciones de IU en Windows, puede acelerar enormemente el proceso de restauración de una ventana cuando llama a System.gc () antes de minimizar la ventana (especialmente cuando permanece minimizada durante bastante tiempo y partes del proceso se cambian a disco).WeakReference
s para los objetos a los que desea retener es incorrecto desde el principio, recolección de basura o no. Tendría el mismo problema en C ++ constd::weak_ptr
(aunque podría notar el problema en una versión de C ++ antes de lo que lo haría en una versión de Java ya que la destrucción de objetos no se aplazaría como suele ser la finalización).System.gc()
arregle es una solución alternativa, no una buena práctica de codificación.Ya se ha explicado que las llamadas
system.gc()
pueden no hacer nada y que cualquier código que "necesita" que el recolector de basura se ejecute está roto.Sin embargo, la razón pragmática por la que es una mala práctica llamar
System.gc()
es porque es ineficiente. Y en el peor de los casos, ¡es terriblemente ineficiente ! Dejame explicar.Un algoritmo típico de GC identifica basura al atravesar todos los objetos que no son basura en el montón e inferir que cualquier objeto no visitado debe ser basura. A partir de esto, podemos modelar el trabajo total de una recolección de basura que consta de una parte que es proporcional a la cantidad de datos en vivo, y otra parte que es proporcional a la cantidad de basura; es decir
work = (live * W1 + garbage * W2)
.Ahora suponga que hace lo siguiente en una aplicación de subproceso único.
La primera llamada (predecimos)
(live * W1 + garbage * W2)
funcionará y eliminará la basura pendiente.La segunda llamada
(live* W1 + 0 * W2)
funcionará y no reclamará nada. En otras palabras, hemos trabajado(live * W1)
y no hemos logrado absolutamente nada .Podemos modelar la eficiencia del recolector como la cantidad de trabajo necesaria para recolectar una unidad de basura; es decir
efficiency = (live * W1 + garbage * W2) / garbage
. Entonces, para que el GC sea lo más eficiente posible, necesitamos maximizar el valor degarbage
cuando ejecutamos el GC; es decir, esperar hasta que el montón esté lleno. (Y también, haga que el montón sea lo más grande posible. Pero ese es un tema aparte).Si la aplicación no interfiere (llamando
System.gc()
), el GC esperará hasta que el montón esté lleno antes de ejecutarse, lo que resulta en una recolección eficiente de basura 1 . Pero si la aplicación obliga a ejecutar el GC, lo más probable es que el montón no esté lleno, y el resultado será que la basura se recolecta de manera ineficiente. Y cuanto más a menudo la aplicación fuerza al GC, más ineficiente se vuelve el GC.Nota: la explicación anterior pasa por alto el hecho de que un GC moderno típico divide el montón en "espacios", el GC puede expandir dinámicamente el montón, el conjunto de trabajo de la aplicación de objetos no basura puede variar, etc. Aun así, el mismo principio básico se aplica en todos los ámbitos a todos los recolectores de basura verdaderos 2 . Es ineficiente forzar el funcionamiento del GC.
1 - Así es como funciona el recopilador de "rendimiento". Los recolectores concurrentes como CMS y G1 utilizan diferentes criterios para decidir cuándo iniciar el recolector de basura.
2 - También estoy excluyendo los administradores de memoria que usan el recuento de referencias exclusivamente, pero ninguna implementación actual de Java usa ese enfoque ... por una buena razón.
fuente
Parece que mucha gente te está diciendo que no hagas esto. Estoy en desacuerdo. Si, después de un gran proceso de carga, como cargar un nivel, cree que:
no hay daño en llamar a System.gc (). Lo veo como la
inline
palabra clave c / c ++ . Es solo una pista para el gc de que usted, el desarrollador, ha decidido que el tiempo / rendimiento no es tan importante como suele ser y que parte de esto podría utilizarse para recuperar la memoria.Consejos para no confiar en que hacer nada es correcto. No confíes en que funcione, pero dar la pista de que ahora es un momento aceptable para recolectar está perfectamente bien. Prefiero perder el tiempo en un punto del código donde no importa (pantalla de carga) que cuando el usuario interactúa activamente con el programa (como durante un nivel de un juego).
Hay un momento en que forzaré recopilación: al intentar averiguar si hay fugas de un objeto en particular (ya sea código nativo o interacción de devolución de llamada grande y compleja. Ah, y cualquier componente de la interfaz de usuario que eche un vistazo a Matlab). Esto nunca debe usarse en código de producción.
fuente
stop the world
enfoque y esto es un verdadero daño, si es que sucedeLa gente ha estado haciendo un buen trabajo explicando por qué NO usarla, así que le contaré un par de situaciones en las que debe usarla:
(Los siguientes comentarios se aplican al Hotspot que se ejecuta en Linux con el recopilador CMS, donde me siento seguro al decir que
System.gc()
de hecho siempre invoca una recolección de basura completa).Después del trabajo inicial de iniciar su aplicación, puede tener un estado terrible de uso de memoria. La mitad de su generación permanente podría estar llena de basura, lo que significa que está mucho más cerca de su primer CMS. En aplicaciones donde eso importa, no es una mala idea llamar a System.gc () para "restablecer" su montón al estado inicial de los datos en vivo.
En la misma línea que el n. ° 1, si monitorea de cerca su uso de almacenamiento dinámico, desea tener una lectura precisa de cuál es su uso de memoria de línea de base. Si los primeros 2 minutos del tiempo de actividad de su aplicación es todo inicialización, sus datos se desordenarán a menos que fuerce (ejem ... "sugerir") el gc completo por adelantado.
Es posible que tenga una aplicación diseñada para nunca promocionar nada a la generación titular mientras se está ejecutando. Pero tal vez necesite inicializar algunos datos por adelantado que no sean tan grandes como para pasar automáticamente a la generación permanente. A menos que llame a System.gc () después de que todo esté configurado, sus datos podrían permanecer en la nueva generación hasta que llegue el momento de promocionarlos. De repente, su aplicación súper duper de baja latencia y baja GC se ve afectada por una penalización de latencia ENORME (relativamente hablando, por supuesto) por promover esos objetos durante las operaciones normales.
A veces es útil tener una llamada System.gc disponible en una aplicación de producción para verificar la existencia de una pérdida de memoria. Si sabe que el conjunto de datos en vivo en el momento X debería existir en una proporción determinada con respecto al conjunto de datos en vivo en el momento Y, entonces podría ser útil llamar a System.gc () un tiempo X y un tiempo Y y comparar el uso de la memoria .
fuente
System.gc()
una vez para forzar la promoción de objetos no gana nada. Y seguramente no desea llamarSystem.gc()
ocho veces seguidas y rezar para que ahora, la promoción se haya realizado y los costos ahorrados de una promoción posterior justifiquen los costos de múltiples GC completos. Dependiendo del algoritmo de GC, la promoción de muchos objetos puede que ni siquiera tenga costos reales, ya que solo reasignará la memoria a la generación anterior o la copiará simultáneamente ...Esta es una pregunta muy molesta, y creo que contribuye a que muchos se opongan a Java a pesar de lo útil que es un lenguaje.
El hecho de que no pueda confiar en "System.gc" para hacer cualquier cosa es increíblemente desalentador y puede invocar fácilmente la sensación de "Miedo, incertidumbre, duda" al lenguaje.
En muchos casos, es bueno lidiar con los picos de memoria que causa a propósito antes de que ocurra un evento importante, lo que haría que los usuarios piensen que su programa está mal diseñado / no responde.
Tener la capacidad de controlar la recolección de basura sería una gran herramienta educativa, que a su vez mejoraría la comprensión de las personas sobre cómo funciona la recolección de basura y cómo hacer que los programas exploten su comportamiento predeterminado y su comportamiento controlado.
Permítanme revisar los argumentos de este hilo.
A menudo, el programa puede no estar haciendo nada y usted sabe que no está haciendo nada debido a la forma en que fue diseñado. Por ejemplo, podría estar haciendo una especie de larga espera con un gran cuadro de mensaje de espera, y al final también podría agregar una llamada para recolectar basura porque el tiempo de ejecución tomará una fracción muy pequeña del tiempo del espere mucho pero evitará que gc actúe en medio de una operación más importante.
No estoy de acuerdo, no importa qué recolector de basura tenga. Su trabajo es rastrear la basura y limpiarla.
Al llamar al gc durante los momentos en que el uso es menos crítico, reduce las probabilidades de que se ejecute cuando su vida se basa en el código específico que se ejecuta, pero en su lugar decide recolectar basura.
Claro, puede que no se comporte de la manera que desea o espera, pero cuando desea llamarlo, sabe que no está sucediendo nada y el usuario está dispuesto a tolerar la lentitud / tiempo de inactividad. Si el System.gc funciona, ¡genial! Si no es así, al menos lo intentaste. Simplemente no hay inconveniente a menos que el recolector de basura tenga efectos secundarios inherentes que hacen algo horriblemente inesperado en cuanto a cómo se supone que debe comportarse un recolector de basura si se invoca manualmente, y esto por sí mismo causa desconfianza.
Es un caso de uso que no puede lograrse de manera confiable, pero podría serlo si el sistema fue diseñado de esa manera. Es como hacer un semáforo y hacer que algunos / todos los botones de los semáforos no hagan nada, te hace preguntarte por qué el botón está ahí para empezar, javascript no tiene la función de recolección de basura, por lo que no No lo analice tanto por eso.
¿Qué es una "pista"? ¿Qué es "ignorar"? una computadora no puede simplemente tomar pistas o ignorar algo, hay caminos de comportamiento estrictos que pueden ser dinámicos y guiados por la intención del sistema. Una respuesta adecuada incluiría lo que el recolector de basura realmente está haciendo, a nivel de implementación, que hace que no realice la recolección cuando lo solicita. ¿Es la función simplemente un nop? ¿Hay algún tipo de condiciones que deba cumplir? ¿Cuáles son estas condiciones?
Tal como está, el GC de Java a menudo parece un monstruo en el que simplemente no confías. No sabes cuándo va a ir o venir, no sabes qué va a hacer, cómo va a hacerlo. Me imagino que algunos expertos tienen una mejor idea de cómo funciona su Recolección de Basura por instrucción, pero la gran mayoría simplemente espera que "simplemente funcione", y tener que confiar en un algoritmo aparentemente opaco para que funcione para usted es frustrante.
Existe una gran brecha entre leer acerca de algo o aprender algo, y realmente ver su implementación, las diferencias entre sistemas y poder jugar con él sin tener que mirar el código fuente. Esto crea confianza y sentimiento de dominio / comprensión / control.
Para resumir, hay un problema inherente con las respuestas "esta característica podría no hacer nada, y no entraré en detalles sobre cómo saber cuándo hace algo y cuándo no hace y por qué no lo hará o lo hará, a menudo implica que es simplemente contra la filosofía intentar hacerlo, incluso si la intención detrás de esto es razonable ".
Puede estar bien que Java GC se comporte de la manera en que lo hace, o puede que no, pero para entenderlo, es difícil seguir realmente en qué dirección ir para obtener una visión general completa de lo que puede confiar en el GC y no hacer, por lo que es demasiado fácil simplemente desconfiar del lenguaje, porque el propósito de un lenguaje es tener un comportamiento controlado hasta un punto filosófico (es fácil para un programador, especialmente los novatos caer en una crisis existencial debido a ciertos comportamientos del sistema / lenguaje) son capaces de tolerar (y si no puedes, simplemente no usarás el lenguaje hasta que tengas que hacerlo), y más cosas que no puedes controlar sin razón conocida por la que no puedes controlarlas son inherentemente dañinas.
fuente
La eficiencia de GC se basa en una serie de heurísticas. Por ejemplo, una heurística común es que los accesos de escritura a objetos generalmente ocurren en objetos que se crearon no hace mucho tiempo. Otra es que muchos objetos tienen una vida muy corta (algunos objetos se usarán durante mucho tiempo, pero muchos se descartarán unos microsegundos después de su creación).
Llamar
System.gc()
es como patear el GC. Significa: "todos esos parámetros cuidadosamente ajustados, esas organizaciones inteligentes, todo el esfuerzo que solo pones en asignar y administrar los objetos de manera que las cosas salgan bien, bueno, simplemente deja todo y comienza desde cero". Se puede mejorar el rendimiento, pero la mayoría de las veces sólo se degrada el rendimiento.Para usar de
System.gc()
manera confiable (*) necesita saber cómo funciona el GC en todos sus detalles. Tales detalles tienden a cambiar bastante si usa una JVM de otro proveedor, o la próxima versión del mismo proveedor, o la misma JVM pero con opciones de línea de comandos ligeramente diferentes. Por lo tanto, rara vez es una buena idea, a menos que desee abordar un problema específico en el que controle todos esos parámetros. De ahí la noción de "mala práctica": eso no está prohibido, el método existe, pero rara vez vale la pena.(*) Estoy hablando de eficiencia aquí.
System.gc()
nunca romperá un programa Java correcto. Tampoco evocará memoria adicional que la JVM no podría haber obtenido de otra manera: antes de lanzar unaOutOfMemoryError
, la JVM hace el trabajoSystem.gc()
, incluso como último recurso.fuente
A veces (¡ no a menudo! ) Realmente sabe más sobre el uso de la memoria pasada, actual y futura que el tiempo de ejecución. Esto no sucede muy a menudo, y afirmaría que nunca en una aplicación web mientras se sirven las páginas normales.
Hace muchos años trabajo en un generador de informes, que
En primer lugar, como no era en tiempo real y los usuarios esperaban un informe, un retraso mientras la ejecución del GC no era un problema, pero necesitábamos generar informes a un ritmo más rápido de lo solicitado.
Mirando el esquema anterior del proceso, queda claro que.
Por lo tanto, claramente valía la pena hacer una ejecución de GC siempre que la cola de solicitudes estuviera vacía; No había inconveniente en esto.
Puede valer la pena realizar una ejecución de GC después de que se envíe cada informe por correo electrónico, ya que sabemos que este es un buen momento para una ejecución de GC. Sin embargo, si la computadora tuviera suficiente memoria RAM, se obtendrían mejores resultados al retrasar la ejecución del GC.
Este comportamiento se configuró en una base por instalación, para algunos clientes que habilitan un GC forzado después de cada informe acelerado la protección de los informes. (Supongo que esto se debió a la poca memoria en su servidor y a la ejecución de muchos otros procesos, por lo que un buen tiempo obligó a GC a reducir la paginación).
Nunca detectamos que una instalación que no se beneficiaba era una ejecución forzada del GC cada vez que la cola de trabajo estaba vacía.
Pero, seamos claros, lo anterior no es un caso común.
fuente
Tal vez escribo un código malo, pero me he dado cuenta de que hacer clic en el icono de la papelera en los IDEs de eclipse y netbeans es una 'buena práctica'.
fuente
System.gc()
periódicamente, probablemente encontraría el comportamiento molesto.Primero, hay una diferencia entre especificaciones y realidad. La especificación dice que System.gc () es una pista de que GC debe ejecutarse y la VM es libre de ignorarlo. La realidad es que la VM nunca ignorará una llamada a System.gc ().
Llamar a GC viene con una sobrecarga no trivial a la llamada y si haces esto en algún momento aleatorio, es probable que no veas ninguna recompensa por tus esfuerzos. Por otro lado, es muy probable que una colección desencadenada naturalmente recupere los costos de la llamada. Si tiene información que indica que se debe ejecutar un GC, puede llamar a System.gc () y debería ver los beneficios. Sin embargo, según mi experiencia, esto sucede solo en algunos casos extremos, ya que es muy poco probable que tenga suficiente información para comprender si se debe llamar a System.gc ().
Un ejemplo enumerado aquí, golpear el bote de basura en su IDE. Si te vas a una reunión, ¿por qué no aciertas? Los gastos generales no le afectarán y el montón podría limpiarse para cuando regrese. ¡Haga esto en un sistema de producción y las frecuentes llamadas a cobrar lo detendrán! Incluso las llamadas ocasionales como las realizadas por RMI pueden ser perjudiciales para el rendimiento.
fuente
Sí, llamar a System.gc () no garantiza que se ejecutará, es una solicitud a la JVM que puede ignorarse. De los documentos:
Casi siempre es una mala idea llamarlo porque la administración automática de memoria generalmente sabe mejor que usted cuándo usar gc. Lo hará cuando su grupo interno de memoria libre sea bajo, o si el sistema operativo solicita que se le devuelva algo de memoria.
Puede ser aceptable llamar a System.gc () si sabe que ayuda. Con eso quiero decir que ha probado y medido a fondo el comportamiento de ambos escenarios en la plataforma de implementación , y puede mostrar que ayuda. Sin embargo, tenga en cuenta que el gc no es fácilmente predecible: puede ayudar en una carrera y dañar en otra.
fuente
En mi experiencia, usar System.gc () es efectivamente una forma de optimización específica de la plataforma (donde "plataforma" es la combinación de arquitectura de hardware, sistema operativo, versión JVM y posibles parámetros de tiempo de ejecución más, como RAM disponible), debido a su comportamiento, Si bien es más o menos predecible en una plataforma específica, puede (y variará) considerablemente entre plataformas.
Sí, no son situaciones en las que System.gc () mejorará el rendimiento (percibida). Un ejemplo es si los retrasos son tolerables en algunas partes de su aplicación, pero no en otras (el ejemplo del juego mencionado anteriormente, donde desea que ocurra GC al comienzo de un nivel, no durante el nivel).
Sin embargo, si ayudará o perjudicará (o no hará nada) depende en gran medida de la plataforma (como se definió anteriormente).
Por lo tanto, creo que es válido como una optimización de plataforma específica de último recurso (es decir, si otras optimizaciones de rendimiento no son suficientes). Pero nunca debe llamarlo solo porque cree que podría ayudar (sin puntos de referencia específicos), porque es probable que no lo haga.
fuente
Dado que los objetos se asignan dinámicamente utilizando el nuevo operador,
es posible que se pregunte cómo se destruyen dichos objetos y se
libera su memoria para su posterior reasignación.
En algunos lenguajes, como C ++, los objetos asignados dinámicamente deben liberarse manualmente mediante el uso de un operador de eliminación.
fuente