Máquina virtual Java: recolección de basura
El ciclo de vida de un objeto Java lo gestiona la JVM. Una vez que el programador crea un objeto, no debemos preocuparnos por el resto de su ciclo de vida. La JVM encontrará automáticamente aquellos objetos que ya no estén en uso y recuperará su memoria del montón.
La recolección de basura es una operación importante que hace JVM y ajustarla a nuestras necesidades puede dar un impulso masivo al rendimiento de nuestra aplicación. Existe una variedad de algoritmos de recolección de basura que son proporcionados por las JVM modernas. Necesitamos ser conscientes de las necesidades de nuestra aplicación para decidir qué algoritmo utilizar.
No puede desasignar un objeto mediante programación en Java, como puede hacer en lenguajes que no son de GC como C y C ++. Por lo tanto, no puede tener referencias colgantes en Java. Sin embargo, puede tener referencias nulas (referencias que se refieren a un área de memoria donde la JVM nunca almacenará objetos). Siempre que se utiliza una referencia nula, la JVM lanza una NullPointerException.
Tenga en cuenta que, si bien es raro encontrar pérdidas de memoria en los programas Java gracias al GC, ocurren. Crearemos una fuga de memoria al final de este capítulo.
Los siguientes GC se utilizan en JVM modernas
- Colector en serie
- Colector de rendimiento
- Recopilador de CMS
- Colector G1
Cada uno de los algoritmos anteriores hace la misma tarea: encontrar objetos que ya no están en uso y recuperar la memoria que ocupan en el montón. Uno de los enfoques ingenuos para esto sería contar el número de referencias que tiene cada objeto y liberarlo tan pronto como el número de referencias se vuelva 0 (esto también se conoce como recuento de referencias). ¿Por qué es esto ingenuo? Considere una lista enlazada circular. Cada uno de sus nodos tendrá una referencia a él, pero no se hace referencia a todo el objeto desde ningún lugar y, idealmente, debería liberarse.
La JVM no solo libera la memoria, sino que también fusiona pequeños chucks de memoria en otros más grandes. Esto se hace para prevenir la fragmentación de la memoria.
En una nota simple, un algoritmo GC típico realiza las siguientes actividades:
- Encontrar objetos no utilizados
- Liberando la memoria que ocupan en el montón
- Fusionando los fragmentos
El GC tiene que detener los subprocesos de la aplicación mientras se está ejecutando. Esto se debe a que mueve los objetos cuando se ejecuta y, por lo tanto, esos objetos no se pueden utilizar. Tales paradas se denominan 'pausas de parada-el-mundo y minimizar la frecuencia y duración de estas pausas es lo que buscamos al sintonizar nuestro GC.
Fusión de memoria
A continuación se muestra una demostración simple de la fusión de la memoria.
La parte sombreada son objetos que deben liberarse. Incluso después de que se recupere todo el espacio, solo podemos asignar un objeto de tamaño máximo = 75Kb. Esto es incluso después de que tengamos 200 Kb de espacio libre como se muestra a continuación.