java - example - ¿Por qué es una mala práctica llamar a System.gc()?
garbage collector java example (13)
Dado que los objetos se asignan dinámicamente mediante el uso del nuevo operador,
usted podría preguntarse cómo se destruyen tales objetos y su
Memoria liberada para su posterior reasignación.En algunos idiomas, como C ++, los objetos asignados dinámicamente se deben liberar manualmente mediante el uso de un operador de eliminación.
- Java tiene un enfoque diferente; se encarga de la desasignación de forma automática.
- La técnica que logra esto se llama recolección de basura. Funciona así: cuando no existen referencias a un objeto, se supone que ese objeto ya no es necesario, y la memoria ocupada por el objeto puede ser reclamada. No hay una necesidad explícita de destruir objetos como en C ++.
- La recolección de basura solo ocurre esporádicamente (si es que ocurre) durante la ejecución de su programa.
- No ocurrirá simplemente porque existen uno o más objetos que ya no se usan.
- Además, las diferentes implementaciones en tiempo de ejecución de Java adoptarán diferentes enfoques para la recolección de basura, pero en su mayor parte, no debería tener que pensar en ello al escribir sus programas.
Después de answering una pregunta sobre cómo liberar objetos con fuerza en Java (el tipo estaba limpiando un HashMap de 1.5GB) con System.gc()
, me dijeron que es una mala práctica llamar a System.gc()
manualmente, pero los comentarios no fueron enteramente convincente Además, nadie parecía atreverse a votar, ni a bajar mi respuesta.
Allí me dijeron que es una mala práctica, pero también me dijeron que las operaciones de recolección de basura ya no detienen el mundo sistemáticamente, y que la JVM también podría usarla efectivamente como una sugerencia, por lo que soy un poco en la 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 los megabytes de datos no son lo que eran hace unos años. Pero aún así, ¿1,5 gigabytes? Y sabes que hay como 1.5 GB de datos en la memoria; No es como si fuera un tiro en la oscuridad. ¿Es System.gc()
sistemáticamente malo, o hay algún punto en el que se vuelve correcto?
Así que la pregunta es en realidad doble:
- ¿Por qué es o no es una mala práctica llamar a
System.gc()
? ¿Es realmente solo una sugerencia para la JVM en ciertas implementaciones, o es siempre un ciclo de recopilación completo? ¿Existen realmente implementaciones de recolectores de basura que puedan hacer su trabajo sin detener al mundo? Por favor, arroje algo de luz sobre las diversas afirmaciones que las personas han hecho en los comentarios a mi answering . - ¿Dónde está el umbral? ¿ Nunca es una buena idea llamar a
System.gc()
, o hay veces en que es aceptable? Si es así, ¿cuáles son esos tiempos?
A veces (¡ no a menudo! ) Realmente sabe más sobre el uso de memoria pasado, actual y futuro que el tiempo de ejecución. Esto no sucede muy a menudo, y yo 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
- Tenía un solo hilo
- Lea la "solicitud de informe" de una cola
- Cargado los datos necesarios para el informe de la base de datos.
- Generé el informe y lo envié por correo electrónico.
- Repetido para siempre, durmiendo cuando no había peticiones pendientes.
- No reutilizó ningún dato entre informes y no realizó ningún cambio.
En primer lugar, ya que no era en tiempo real y los usuarios esperaban esperar un informe, un retraso en la ejecución del GC no era un problema, pero necesitábamos producir informes a una velocidad más rápida de lo que se solicitaba.
En cuanto al esquema anterior del proceso, está claro que.
- Sabemos que habrá muy pocos objetos vivos justo después de que un informe haya sido enviado por correo electrónico, ya que la próxima solicitud aún no se había procesado.
- Es bien sabido que el costo de ejecutar un ciclo de recolección de basura depende del número de objetos vivos, la cantidad de basura tiene poco efecto en el costo de una ejecución de GC.
- Que cuando la cola está vacía no hay nada mejor que hacer que ejecutar el GC.
Por lo tanto, claramente valió la pena realizar una ejecución de GC siempre que la cola de solicitudes estuviera vacía; No había ningún inconveniente en esto.
Puede valer la pena realizar una ejecución de GC después de 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 retrasando la ejecución del GC.
Este comportamiento se configuró para cada instalación, para algunos clientes que habilitaron un GC forzado después de que cada informe aceleró en gran medida la protección de los informes. (Espero que esto se deba a la poca memoria en su servidor y que se ejecute en muchos otros procesos, por lo que, por lo tanto, es un tiempo forzado por el GC la paginación reducida)
Nunca detectamos que una instalación que no se benefició fue una ejecución forzada de GC cada vez que la cola de trabajo estaba vacía.
Pero, seamos claros, lo anterior no es un caso común.
En mi experiencia, el uso de 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 de JVM y posibles parámetros de tiempo de ejecución más, como RAM disponible), debido a su comportamiento, Si bien es bastante predecible en una plataforma específica, puede (y lo hará) variar considerablemente entre plataformas.
Sí, hay situaciones en las que System.gc () mejorará (percibirá) el rendimiento. El ejemplo es si los retrasos son tolerables en algunas partes de su aplicación, pero no en otras (el ejemplo del juego citado anteriormente, donde desea que ocurra GC al inicio 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).
Así que creo que es válido como una optimización específica de la plataforma 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 sea así.
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 algo es increíblemente desalentador y puede invocar fácilmente la sensación de "Miedo, incertidumbre, duda" en el idioma.
En muchos casos, es bueno lidiar con los picos de memoria que provoca a propósito antes de que ocurra un evento importante, lo que podría hacer que los usuarios piensen que su programa está mal diseñado o no responde.
Tener la capacidad de controlar la recolección de basura sería una excelente herramienta educativa, que a su vez mejorará la comprensión de la gente sobre cómo funciona la recolección de basura y cómo hacer que los programas exploten su comportamiento predeterminado así como el comportamiento controlado.
Déjame revisar los argumentos de este hilo.
- Es ineficiente:
A menudo, es posible que el programa no esté haciendo nada y usted sabe que no está haciendo nada por la forma en que fue diseñado. Por ejemplo, podría estar haciendo una especie de espera larga con un cuadro de mensaje de espera grande, y al final también puede agregar una llamada para recolectar basura porque el tiempo para ejecutarlo tomará una fracción muy pequeña del tiempo del evento. espere mucho, pero evitará que gc actúe en medio de una operación más importante.
- Siempre es una mala práctica e indica código roto.
No estoy de acuerdo, no importa qué recolector de basura tengas. Su trabajo es rastrear la basura y limpiarla.
Al llamar al gc en momentos en que el uso es menos crítico, reduce las probabilidades de que se ejecute cuando su vida depende del código específico que se está ejecutando, pero en su lugar, decide recolectar basura.
Claro, puede que no se comporte de la manera que usted quiere o espera, pero cuando quiere llamarlo, sabe que no está pasando nada y el usuario está dispuesto a tolerar la lentitud / 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 hagan algo horriblemente inesperado sobre cómo se supone que se comporta un recolector de basura si se invoca manualmente, y esto por sí mismo genera desconfianza.
- No es un caso de uso común:
Es un caso de uso que no se puede lograr de manera confiable, pero podría serlo si el sistema se diseñó de esa manera. Es como hacer un semáforo y hacer que algunos / todos los botones de los semáforos no hagan nada, hace que te preguntes por qué el botón está ahí para empezar, javascript no tiene función de recolección de basura, por lo que no No lo escudriñes tanto por ello.
- La especificación dice que System.gc () es una sugerencia de que GC debería ejecutarse y que la VM es libre de ignorarlo.
¿Qué es una "pista"? ¿Qué es "ignorar"? una computadora no puede simplemente dar pistas o ignorar algo, hay rutas de comportamiento estrictas que pueden ser dinámicas y están guiadas por la intención del sistema. Una respuesta adecuada incluiría lo que realmente está haciendo el recolector de basura, en el nivel de implementación, que hace que no realice una recopilación cuando la solicite. ¿Es la característica simplemente un nop? ¿Hay algún tipo de condiciones que me deban cumplir? ¿Cuáles son estas condiciones?
Tal como está, el GC de Java a menudo parece un monstruo en el que no confías. No sabes cuándo va a venir o ir, no sabes qué va a hacer, cómo lo va a hacer. Puedo imaginar a algunos expertos teniendo una mejor idea de cómo funciona su recolección de basura en base a cada instrucción, pero la gran mayoría simplemente espera que "simplemente funcione", y tener que confiar en un algoritmo de apariencia opaca para que trabaje por usted es frustrante.
Existe una gran brecha entre la lectura de algo o la enseñanza de algo, y el hecho de 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 decir cuándo hace algo y cuándo no y por qué no lo hará o no lo hará, a menudo implica que es simplemente contra la filosofía tratar de 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 hacerlo, por lo que es demasiado fácil simplemente desconfiar del lenguaje, porque el propósito de un lenguaje es tener un comportamiento controlado hasta el punto filosófico (es fácil para un programador, especialmente para los principiantes caer en una crisis existencial de ciertos comportamientos del sistema / lenguaje) son capaces de tolerar (y si no puede, simplemente no usará el lenguaje hasta que tenga que hacerlo), y más cosas que no puede controlar por ninguna razón conocida por la que no puede controlarlas es inherentemente dañino.
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 usualmente ocurren en objetos que fueron creados no hace mucho tiempo. Otra es que muchos objetos tienen una vida muy corta (algunos se utilizarán durante mucho tiempo, pero muchos serán descartados unos pocos microsegundos después de su creación).
Llamar a System.gc()
es como patear el GC. Significa: "todos esos parámetros cuidadosamente ajustados, esas organizaciones inteligentes, todo el esfuerzo que acaba de poner en la asignación y administración de los objetos para que las cosas vayan bien, bueno, simplemente deje caer todo el lote y comience de cero". Puede mejorar el rendimiento, pero la mayoría de las veces solo degrada el rendimiento.
Para usar System.gc()
forma confiable (*), debe saber cómo funciona el GC en todos sus detalles. Dichos detalles tienden a cambiar bastante si utiliza 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 un OutOfMemoryError
, la JVM hace el trabajo de System.gc()
, incluso como último recurso.
La gente ha estado haciendo un buen trabajo explicando por qué NO usar, así que les diré un par de situaciones en las que deben usarlo:
(Los siguientes comentarios se aplican a Hotspot que se ejecuta en Linux con el recopilador de CMS, donde me siento seguro al decir que System.gc()
en realidad siempre invoca una recolección de basura completa).
Después del trabajo inicial de inicio de la aplicación, es posible que se encuentre en un estado terrible de uso de memoria. La mitad de tu generación podría estar llena de basura, lo que significa que estás mucho más cerca de tu primer CMS. En las aplicaciones donde eso importa, no es una mala idea llamar a System.gc () para "restablecer" su montón al estado de inicio de los datos en vivo.
En la misma línea que el número 1, si supervisa de cerca el uso del montón, desea tener una lectura precisa de cuál es el uso de la memoria base. Si los primeros 2 minutos del tiempo de actividad de su aplicación son todos de inicialización, sus datos se desordenarán a menos que fuerce (ejem ... "sugiera") el gc completo por adelantado.
Es posible que tenga una aplicación que esté diseñada para nunca promover 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 que se trasladen automáticamente a la generación actual. A menos que llame a System.gc () después de que todo esté configurado, sus datos podrían ubicarse en la nueva generación hasta que llegue el momento de su promoción. De repente, su aplicación super-duper de baja latencia y baja GC se ve afectada por una gran penalización de latencia (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 debe existir en una cierta proporción al conjunto de datos en vivo en el momento Y, entonces podría ser útil llamar a System.gc () una hora X y una hora Y y comparar el uso de la memoria .
La razón por la que todo el mundo siempre dice que se debe evitar System.gc()
es que es un indicador bastante bueno de un código fundamentalmente dañado . Cualquier código que dependa de él para la corrección está ciertamente roto; cualquiera que confíe en él para el rendimiento es más probable que se rompa.
No sabes a qué tipo de recolector de basura estás ejecutando. Ciertamente, hay algunos que no "detienen al mundo" como usted afirma, pero algunas JVM no son tan inteligentes o por varias razones (¿quizás están en un teléfono?) No lo hacen. No sabes lo que va a hacer.
Además, no se garantiza hacer nada. La JVM puede ignorar por completo su solicitud.
La combinación de "no sabes lo que hará", "ni siquiera sabes si te ayudará" y "no deberías llamarlo de todos modos" es la razón por la que las personas son tan enérgicas al decir que generalmente no deberias llamarlo Creo que es un caso de "si necesitas preguntar si deberías usar esto, no deberías"
EDITAR para abordar algunas preocupaciones del otro hilo:
Después de leer el hilo que has vinculado, hay algunas cosas más que me gustaría señalar. Primero, alguien sugirió que llamar a gc()
podría devolver la memoria al sistema. Eso no es necesariamente cierto: el mismo montón de Java crece independientemente de las asignaciones de Java.
Al igual que en, la JVM mantendrá la memoria (muchas decenas de megabytes) y hará crecer el montón según sea necesario. No necesariamente devuelve esa memoria al sistema incluso cuando libera objetos Java; es perfectamente gratuito 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 -XX:DisableExplicitGC VM.
Mis 2 centavos: carga algunos AnimationDrawables en una actividad y los juego. Cargo, juego y luego establezco el fondo de la vista de imagen en nulo, uno a la vez. Si salgo de la actividad y luego vuelvo rápidamente, después de 3 o 4 veces, la memoria ocupada crece demasiado hasta que obtengo una excepción de memoria insuficiente.
Al llamar explícitamente al recolector de basura después de configurar el fondo de visualización de imagen en nulo, veo en Eclipse logcat que la memoria se mantiene lo suficientemente libre, y en mi caso, gc se ejecuta en realidad, y ya no consigo que la aplicación deje de funcionar.
Es obvio que el sistema puede decidir posponer la ejecución de gc, pero si sabe más o menos cómo funciona un gc, puede confiar en un caso como el mío que se llamará lo antes posible, por la razón que el sistema notifica que la memoria se usa y crece. La aplicación está a punto de pedir más al sistema. Creo que funciona como los contenedores de la biblioteca c ++ std: obtienes algo de memoria de inicio y cada vez no es suficiente, se duplica.
Decir que si necesita llamarlo es debido a un código roto o defectuoso es una forma dogmática irrazonable de contestarme: especialmente si puede programar en un lenguaje con administración de memoria manual total como C ++ y tiene que enfrentar el límite de recursos en un dispositivo móvil con un lenguaje como Java en su lugar, sin posibilidad de liberar memoria manualmente, puede pensar rápidamente en muchas situaciones en las que es necesario llamar explícitamente a gc, especialmente cuando tiene un gc de rastreo y no una referencia contando una, el código Está limpio y bien hecho.
Mucha gente parece estar diciéndote que no hagas esto. Estoy en desacuerdo. Si, después de un proceso de carga grande como cargar un nivel, cree que:
- Tienes muchos objetos que son inalcanzables y es posible que no se hayan gc''ed. y
- Crees que el usuario podría soportar una pequeña desaceleración en este punto
no hay daño en llamar a System.gc (). Lo veo como la palabra clave en inline
c / c ++. Es solo una sugerencia para el CG que usted, el desarrollador, haya decidido que el tiempo / rendimiento no es tan importante como lo es normalmente y que parte de él podría usarse para recuperar memoria.
Consejos para no confiar en que hacer nada es correcto. No confíe en que funcione, pero dar la pista de que ahora es un momento aceptable para recopilar está perfectamente bien. Prefiero perder el tiempo en un punto del código donde no importa (pantalla de carga) que cuando el usuario está interactuando activamente con el programa (como durante un nivel de un juego).
Hay un momento en el que force recopilación: cuando intento descubrir es una fuga de un objeto en particular (ya sea un código nativo o una interacción de devolución de llamada grande y compleja. Ah, y cualquier componente de la interfaz de usuario, tanto como las miradas a Matlab). Esto nunca debe usarse En código de producción.
Primero, hay una diferencia entre la especificación y la realidad. La especificación dice que System.gc () es una sugerencia de que GC debería ejecutarse y que la VM es libre de ignorarlo. La realidad es que la máquina virtual nunca ignorará una llamada a System.gc ().
Llamar a GC viene con una sobrecarga no trivial a la llamada y si lo haces en algún momento al azar, es probable que no veas ninguna recompensa por tus esfuerzos. Por otro lado, es muy probable que una recopilación activada naturalmente recupere los costos de la llamada. Si tiene información que indica que se debe ejecutar un GC, puede hacer una llamada a System.gc () y debería ver los beneficios. Sin embargo, según mi experiencia, esto sucede solo en unos pocos casos de borde, ya que es muy poco probable que tenga suficiente información para comprender si debe llamarse System.gc () y cuándo.
Un ejemplo se lista aquí, golpeando el bote de basura en su IDE. Si vas a una reunión, ¿por qué no lo golpeas? La sobrecarga no te afectará y el montón puede ser limpiado para cuando vuelvas. ¡Haga esto en un sistema de producción y las llamadas frecuentes para recolectar lo detendrán! Incluso las llamadas ocasionales como las realizadas por RMI pueden ser perjudiciales para el rendimiento.
Sí, llamar a System.gc () no garantiza que se ejecutará, es una solicitud a la JVM que puede ignorarse. De la documentación:
Llamar al método gc sugiere que la máquina virtual de Java haga un esfuerzo para reciclar objetos no utilizados
Casi siempre es una mala idea llamarlo porque la administración automática de la memoria por lo general sabe mejor que tú cuándo 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.
Podría ser aceptable llamar a System.gc () si sabe que esto ayuda. Con esto 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 perjudicar a otra.
Tal vez escribo código de mierda, pero me he dado cuenta de que hacer clic en el icono de la papelera en los IDE de eclipse y netbeans es una "buena práctica".
Ya se ha explicado que llamar a system.gc()
no puede hacer nada, y que cualquier código que "necesite" el recolector de basura para ejecutarse está roto.
Sin embargo, la razón pragmática de que es una mala práctica llamar a System.gc()
es que es ineficiente. ¡Y en el peor de los casos, es horriblemente ineficiente ! Dejame explicar.
Un algoritmo GC típico identifica la 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 consiste en 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 un solo hilo.
System.gc(); System.gc();
La primera llamada (predecimos) funcionará (live * W1 + garbage * W2)
y se librará de la basura pendiente.
La segunda llamada funcionará (live* W1 + 0 * W2)
y no reclamará nada. En otras palabras, hemos hecho (live * W1)
trabajo y no hemos logrado absolutamente nada .
Podemos modelar la eficiencia del recolector como la cantidad de trabajo necesario 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 de la garbage
cuando ejecutamos el GC; es decir, esperar hasta que el montón esté lleno. (Y también, haz el montón lo más grande posible. Pero ese es un tema aparte).
Si la aplicación no interfiere (al llamar a System.gc()
), el GC esperará hasta que el montón esté lleno antes de ejecutarse, lo que resultará en una recolección eficiente de basura 1 . Pero si la aplicación obliga a que se ejecute el GC, es probable 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 obliga a 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 y así sucesivamente. 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 recolector 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 a los administradores de memoria que usan el conteo de referencias exclusivamente, pero ninguna implementación actual de Java usa ese enfoque ... por una buena razón.