Recolección de basura de aprendizaje de Java

Antes de hablar sobre la recolección de basura, permítanme explicar dónde se necesita el recolector de basura de la JVM en la estructura de la memoria y cuáles no.
Generalmente hay tres formas de asignación de memoria:

  1. Asignado desde el área de almacenamiento estático. Asignado en tiempo de compilación, como variables estáticas.
  2. Creado en la pila. Las variables locales de varios tipos de datos básicos se crean en la pila. Cuando el programador sale del alcance de la variable, su memoria se libera automáticamente y, naturalmente, el recolector de basura no se usa.
  3. Creado en el montón. Los objetos asignan memoria en la cola cuando se crean. El recolector de basura controla el espacio del montón. El recolector de basura se ejecuta como un hilo independiente.

Se puede ver que el recolector de basura se centra más en el espacio del montón.

Antes de hablar sobre el mecanismo de recolección de basura, primero comprendamos el ciclo de vida de los objetos y las variables.

El ciclo de vida de las variables locales y las variables de referencia: Mientras no se haya ejecutado el método en el que se ubica la variable local, la variable local sigue viva.
El ciclo de vida de las variables de instancia (variables miembro): dado que la variable de instancia está en el objeto, el objeto adjunto todavía está vivo y la variable de instancia también está viva.
El ciclo de vida del objeto : si no hay ninguna referencia al objeto, el objeto será expulsado del montón.

¿Cuándo será recuperado el objeto por el recolector de basura?

  1. Las referencias a objetos abandonan su ámbito de forma permanente

    class Duck{
    
    }
    public class StackRef{
        public void foof(){
            barf();
        }   
        public void barf(){
            Duck d=new Duck();
        }
    }

    En el código anterior, cuando se ejecuta el método barf (), la variable de referencia d también deja su alcance, por lo que el recolector de basura reclamará el espacio variable aplicado por new Duck () en el montón

  2. Las referencias se copian a otros objetos

    public class ReRef{
        Duck d=new Duck();
        public void go(){
            d=new Duck();
        }
    }

    En el ejemplo anterior, la referencia del objeto se restablece y la dirección del espacio de un nuevo objeto se almacena. Luego, el espacio apuntado originalmente no tiene otras referencias apuntando a él, y naturalmente será reciclado por el recolector de basura.

  3. Establezca directamente la referencia en nulo

public class ReRef{
    Duck d=new Duck();
    public void go(){
        d=null;
    }
}

Null representa una combinación de bytes vacía. Este caso es similar al segundo caso, excepto que el valor de la variable de referencia se establece en nulo, y de nuevo no haré demasiada descripción.

El siguiente contenido se reproduce en: http://blog.csdn.net/zhangerqing

Uno, recolección de basura

Hay un dicho: hay un muro entre Java y C ++ con asignación de memoria y tecnología de recolección de basura. ¡La gente fuera del muro quiere entrar y la gente dentro del muro quiere salir! El significado de esta oración, considérelo usted mismo. En general, los programadores de C y C ++ a veces sufren pérdidas de memoria, y la gestión de la memoria es un dolor de cabeza, pero los programadores de Java, y los programadores de C ++ envidian, pueden controlar todo por sí mismos para que no estén en la gestión de la memoria. Parece impotente, pero Es así. Como programadores de Java, es difícil para nosotros controlar la recuperación de memoria de la JVM. Solo podemos adaptarnos de acuerdo a sus principios para mejorar el rendimiento del programa tanto como sea posible. Comencemos a explicar la recolección de basura de Java, a saber, Garbage Collection, GC. De los siguientes cuatro aspectos:

1. ¿Por qué necesitamos realizar una recolección de basura?

A medida que se ejecuta el programa, más y más memoria está ocupada por objetos de instancia, variables y otra información en la memoria. Si la recolección de basura no se realiza a tiempo, el rendimiento del programa disminuirá inevitablemente e incluso se puede producir algo de memoria innecesaria debido a a memoria disponible insuficiente El sistema es anormal.

2. ¿Qué "basura" hay que reciclar?

De las cinco áreas que presentamos anteriormente, tres no requieren recolección de basura: el contador del programa, la pila de JVM y la pila de métodos locales. Debido a que su ciclo de vida está sincronizado con el subproceso, a medida que se destruye el subproceso, la memoria que ocupan se liberará automáticamente, por lo que solo el área de método y el montón deben ser GC. Específico para qué objetos, una breve descripción: si un objeto ya no tiene referencias, puede reciclarse. Para explicarlo en términos sencillos, si un objeto no tiene ningún efecto, se puede reciclar como residuo.

3. ¿Cuándo se realizará la recolección de basura?

De acuerdo con un algoritmo clásico de conteo de referencias, cada objeto agrega un contador de referencia. Cada vez que se hace referencia, el contador se incrementa en 1, y la referencia se pierde, y el contador se decrementa. Cuando el contador permanece en 0 por un período de tiempo, el objeto se considera apto para su uso. Sin embargo, este algoritmo tiene fallas obvias: cuando dos objetos se refieren entre sí, pero los dos no tienen ningún efecto, según la convención, deben ser recolectados como basura, pero sus referencias mutuas no cumplen con las condiciones de recolección de basura, por lo que no pueden Sea perfecto Para hacer frente a esta limpieza de memoria, la JVM de Sun no utiliza un algoritmo de recuento de referencias para la recolección de basura. En su lugar, utiliza un algoritmo de búsqueda de raíz, como se muestra en la siguiente figura:

Escriba la descripción de la imagen aquí

La idea básica es: comenzar con un objeto llamado GC Roots y buscar hacia abajo. Si un objeto no puede alcanzar el objeto GC Roots, significa que ya no se hace referencia y se puede recolectar basura (por el momento entendido de esta manera, de hecho De hecho, hay algunas diferencias. Cuando ya no se hace referencia a un objeto, no está completamente "muerto". Si la clase anula el método finalize () y el sistema no la ha llamado, entonces el sistema llamará al finalize ( ) método una vez para finalizar el trabajo final. Durante este período, si el objeto se puede volver a asociar con cualquier objeto referenciado por GC Roots, el objeto puede "renacer", de lo contrario, significa que se puede reciclar por completo), como arriba En la figura, Objeto5, Objeto6 y Objeto7, aunque todavía pueden referirse entre sí, pero en general, no tienen ningún efecto, esto resuelve el problema que el algoritmo de conteo de referencias no puede resolver.

Concepto de referencia complementario: después de JDK 1.2, las referencias se han ampliado, introduciendo cuatro tipos de referencias: fuerte, suave, si y virtual, que se marcan como los cuatro tipos de objetos referenciados, que tienen diferentes significados en GC:

a> Referencia Fuerte. Es una referencia agregada a un objeto que acaba de ser nuevo. Su característica es que nunca será reciclado.

b> Referencia suave. Una clase declarada como referencia suave es un objeto que se puede reciclar. Si la memoria JVM no está ajustada, este tipo de objeto no se puede reciclar. Si la memoria es ajustada, se reciclará. Aquí hay una pregunta: dado que el objeto al que se hace referencia como referencia suave se puede reciclar, ¿por qué no reciclarlo? De hecho, sabemos que hay un mecanismo de caché en Java. Tome la caché literal como ejemplo. A veces, el objeto en caché es actualmente prescindible, pero solo se deja en la memoria si es necesario, y no hay necesidad para reasignar la memoria Se puede usar, por lo que estos objetos se pueden citar como referencias suaves, lo cual es conveniente de usar y mejora el rendimiento del programa.

c> Referencia débil. Los objetos de referencia débiles deben recolectarse basura. Independientemente de si la memoria es escasa o no, cuando se realiza la GC, los objetos marcados como referencias débiles definitivamente se limpiarán y reciclarán.

d> Referencia fantasma. La referencia fantasma es débil y se puede ignorar. JVM no se preocupa en absoluto por la referencia fantasma. Su única función es hacer algunos registros de seguimiento para ayudar al uso de la función de finalización.
En conclusión, ¿qué tipo de clases hay que reciclar? Clases inútiles, ¿qué son las clases inútiles? Necesita cumplir con los siguientes requisitos:

1> Todos los objetos de instancia de esta clase se han reciclado.

2> El ClassLoader que cargó esta clase ha sido reciclado.

3> La clase de reflexión correspondiente java.lang.Class objeto de esta clase no está referenciada en ninguna parte.

4. ¿Cómo se realiza la recogida de basura?

El contenido de este bloque es principalmente para introducir el algoritmo de recolección de basura, como hemos introducido anteriormente, la memoria se divide principalmente en tres bloques, la nueva generación, la vieja generación y la generación persistente. Las características de las tres generaciones son diferentes, lo que da como resultado diferentes algoritmos de GC. La nueva generación es adecuada para objetos con un ciclo de vida corto y que se crean y destruyen con frecuencia. La generación anterior es adecuada para objetos con un ciclo de vida relativamente largo. La generación persistente se refiere al área de métodos HotSpot de Sun (en algunas JVM, no existe la generación persistente en absoluto). Primero introduzca los conceptos y características de la nueva generación, la vieja generación y la generación duradera:

Escriba la descripción de la imagen aquí

Nueva generación : Nueva Generación o Generación Joven. Lo anterior se divide aproximadamente en el área de Eden y el área de Survivor, y el área de Survivor se divide en dos partes del mismo tamaño: FromSpace y ToSpace. La nueva generación asigna memoria a los objetos recién creados. Cuando el espacio del Edén es insuficiente, los objetos supervivientes se transferirán a Survivor. El tamaño de la nueva generación se puede controlar mediante -Xmn, o puedes usar -XX: SurvivorRatio para controlar Eden y Survivor. La proporción de la
generación anterior: la generación anterior. Se utiliza para almacenar objetos de la nueva generación que todavía están vivos después de varias recolecciones de basura, como objetos almacenados en caché. El tamaño ocupado de la generación anterior es el valor de -Xmx menos el valor correspondiente a -Xmn.

Permanente generación : Generación permanente. En la JVM de Sun, significa área de método, aunque la mayoría de las JVM no tienen esta generación. Alguna información que almacena principalmente constantes y clases tiene un valor predeterminado de un mínimo de 16 MB y un máximo de 64 MB. Los valores mínimo y máximo se pueden establecer mediante -XX: PermSize y -XX: MaxPermSize.

Algoritmos GC comunes:

Algoritmo de barrido de marcas (barrido de marcas)

El algoritmo GC más básico marca los objetos que necesitan ser recuperados y luego los escanea y recupera los marcados, lo que da como resultado dos pasos: marcado y limpieza. Este algoritmo no es eficiente y la fragmentación de la memoria se generará una vez finalizada la limpieza, de esta forma si hay objetos grandes que requieren un espacio de memoria continuo, también es necesario desfragmentarlo, por lo que este algoritmo debe mejorarse.

Copiar algoritmo (Copiar)

Como mencionamos anteriormente, la nueva generación de memoria se divide en tres partes, el área de Edén y dos áreas de Superviviente. Generalmente, la JVM de Sun ajustará la proporción del área de Edén al área de Superviviente a 8: 1 para asegurar que un área de Superviviente es gratis, por lo que durante la recolección de basura, coloque los objetos que no necesitan ser reciclados en el área libre de Supervivientes, y luego limpie completamente el área de Edén y la primera área de Superviviente. Hay un problema, es decir, si el espacio del La segunda zona de supervivientes no es suficiente ¿Qué hacer? En este momento, cuando el área de Superviviente no es suficiente, debe tomar prestada temporalmente la memoria persistente para usarla. Este algoritmo es adecuado para la nueva generación.

Algoritmo Mark-Compact (o llamado compresión) (Mark-Compact)

Lo mismo que la primera mitad del algoritmo de borrado de marcas, excepto que después de marcar los objetos que no necesitan ser reciclados, los objetos marcados se mueven juntos para hacer que la memoria sea contigua, de modo que solo se limpia la memoria fuera del límite marcado hasta. Este algoritmo es adecuado para generación permanente.

Recolectores de basura comunes:

De acuerdo con los muchos algoritmos mencionados anteriormente, JVM tiene diferentes implementaciones todos los días. Echemos un vistazo a algunos recolectores de basura comunes:

Escriba la descripción de la imagen aquí

Primero introduzca tres recolectores de basura reales: GC serial (SerialGC), GC de recolección paralela (Parallel Scavenge) y GC paralelo (ParNew).

1. GC en serie . Es el recolector más básico y más antiguo, pero todavía se usa ampliamente. Es un mecanismo de recolección de basura de un solo subproceso, y no solo eso, su característica más importante es que todos los subprocesos que se están ejecutando deben suspenderse durante la recolección de basura. ( Stop The World). Para algunas aplicaciones, esto es inaceptable, pero podemos pensarlo de esta manera. Siempre que podamos controlar el tiempo de pausa en N milisegundos, todavía podemos aceptar la mayoría de las aplicaciones, y el hecho es que no ha Nos decepcionó. Una pausa de decenas de milímetros es completamente aceptable para nosotros como cliente. Este colector es adecuado para aplicaciones con una sola CPU, espacio pequeño para la nueva generación y requisitos no muy altos para el tiempo de pausa. El anterior es el método GC predeterminado a nivel de cliente, que se puede especificar de forma forzosa mediante -XX: + UseSerialGC.

2. ParNew GC . Es básicamente lo mismo que Serial GC, pero la diferencia esencial es que agrega un mecanismo de subprocesos múltiples para mejorar la eficiencia, de modo que se puede usar en el lado del servidor (Servidor), y puede cooperar con CMS GC, por lo que no hay más razones para configurarlo. Del lado del servidor.

3. GC de barrido paralelo. Todo el proceso de escaneo y copia se lleva a cabo de manera multiproceso. Es adecuado para aplicaciones con múltiples CPU y tiempo de pausa corto. Es el método GC predeterminado a nivel de servidor. Puede usar -XX: + UseParallelGC para forzar la designación, use-XX: ParallelGCThreads = 4 para especificar el número de subprocesos. Los siguientes son varios grupos de combinaciones de uso:

4. Recopilador CMS (Concurrent Mark Sweep) . El objetivo de este colector es resolver el problema de pausa de Serial GC para lograr el menor tiempo de recuperación. Las aplicaciones de arquitectura B / S comunes son adecuadas para este colector debido a su alta concurrencia y características de alta respuesta. El recopilador de CMS se implementa según el algoritmo de "marca-barrido". Todo el proceso de recopilación se divide aproximadamente en 4 pasos:

Marca inicial de CMS, marca concurrente de CMS, comentario de CMS y barrido concurrente de CMS.

Los dos pasos de marcado inicial y remarcado aún deben suspender otros hilos de usuario. El marcado inicial solo marca los objetos con los que GC ROOTS puede asociarse directamente, y la velocidad es muy rápida La fase de marcado concurrente es la fase del algoritmo de búsqueda de raíz de GC ROOTS, que determinará si el objeto está vivo. La fase de re-marcado es para corregir los registros de marcado de la parte del objeto cuyas marcas han cambiado debido a la operación continua del programa de usuario durante el marcado concurrente. El tiempo de pausa en esta fase será un poco más largo en la fase de marcado inicial , pero más corta que la fase de calificación concurrente. Dado que el subproceso del recopilador puede trabajar con el subproceso del usuario en el proceso de marcado y borrado concurrente más largo de todo el proceso, en general, el proceso de recuperación de memoria del recopilador de CMS se ejecuta al mismo tiempo que el subproceso del usuario.

Las ventajas del recopilador de CMS: recopilación simultánea, pausa baja, pero CMS está lejos de ser perfecto.

El recopilador de CMS tiene tres defectos principales :

a>. El recopilador de CMS es muy sensible a los recursos de la CPU. En la fase concurrente, aunque el hilo del usuario no se detendrá, consumirá recursos de la CPU y hará que el programa de referencia se ralentice y el rendimiento total disminuya. El número de subprocesos de reciclaje iniciados por CMS de forma predeterminada es: (número de CPU + 3) / 4.

b>. El recolector CMS no puede manejar basura flotante y puede aparecer "Fallo de modo concurrente" Después del fallo, se genera otro GC completo. Dado que el hilo del usuario todavía se está ejecutando durante la fase de limpieza simultánea del CMS, se seguirá generando basura nueva a medida que el programa se ejecute automáticamente. Esta parte de la basura aparece después del proceso de marcado. El CMS no puede procesarlos en esta colección. y tiene que guardarlo para el próximo GC. Límpielo. Esta parte de la basura se llama "basura flotante". También se debe a que los subprocesos del usuario aún deben ejecutarse en la fase de recolección de basura, es decir, se debe reservar suficiente espacio de memoria para que los subprocesos del usuario lo usen, por lo que el recolector de CMS no puede esperar hasta que la vejez esté casi completamente llena antes de recopilarlo. al igual que otros colectores, parte del espacio de la memoria se reserva para el funcionamiento del programa durante la recolección concurrente. En la configuración predeterminada, el recopilador CMS se activará cuando el 68% del espacio se use en la generación anterior. El porcentaje de activación también puede ser proporcionado por el valor del parámetro -XX: CMSInitiatingOccupancyFraction para reducir el número de reclamaciones de memoria y mejorar rendimiento. Si la memoria reservada durante el funcionamiento del CMS no puede satisfacer las necesidades de otros subprocesos del programa, se producirá una falla de "Fallo de modo concurrente". En este momento, la máquina virtual iniciará el plan de respaldo: habilite temporalmente el recopilador Serial Old para volver a llevar a cabo la recolección de basura de la generación anterior, por lo que el tiempo de pausa es muy largo. Por lo tanto, configurar el parámetro -XX: CMSInitiatingOccupancyFraction demasiado alto conducirá fácilmente a una falla "Concurrent Mode Failure" y, en cambio, el rendimiento disminuirá.

c>. El último defecto, CMS es un recolector basado en el algoritmo "mark-sweep", luego de usar el algoritmo "mark-sweep" para recolectar, se generará una gran cantidad de fragmentos. Cuando hay demasiada fragmentación de espacio, traerá muchos problemas a la asignación de objetos. Por ejemplo, para objetos grandes, el espacio de memoria no puede encontrar un espacio contiguo para asignar y debe activar un GC completo por adelantado. Para resolver este problema, el recopilador de CMS proporciona un parámetro de cambio -XX: UseCMSCompactAtFullCollection, que se usa para agregar un proceso de desfragmentación después del GC completo. También puede usar el parámetro -XX: CMSFullGCBeforeCompaction para establecer cuántos GC completos sin comprimir se ejecutan , seguido de Hagamos un proceso de desfragmentación.

5. Colector G1. En comparación con el recopilador CMS, hay muchas mejoras. En primer lugar, se basa en el algoritmo de marcado-desfragmentación, que no provocará problemas de fragmentación de la memoria. En segundo lugar, puede controlar la pausa con mayor precisión, que no se describirá en detalle aquí.

6. Serie antigua. Serial Old es la versión antigua del recopilador Serial, que también utiliza un solo hilo para realizar la recopilación, utilizando el algoritmo "marcar y ordenar". Utilice principalmente máquinas virtuales en modo Cliente.

7. Paralelo Antiguo. Parallel Old es la versión antigua del recopilador Parallel Scavenge, que utiliza subprocesos múltiples y un algoritmo de "marcar y ordenar".

8. El recolector de basura RTSJ se utiliza para la programación en tiempo real de Java, que se presentará más adelante.

Dos, optimización del rendimiento del programa Java

gc () llamar

Llamar al método gc implica que la máquina virtual Java ha realizado algunos esfuerzos para recuperar los objetos no utilizados para que la memoria que estos objetos ocupan actualmente se pueda reutilizar rápidamente. Cuando el control regresa de la llamada al método, la máquina virtual ha hecho todo lo posible para recuperar espacio de todos los objetos descartados. Llamar a System.gc () es equivalente a llamar a Runtime.getRuntime (). Gc ().

Llamar y reescribir finalize ()

gc solo puede borrar la memoria asignada en el montón (todos los objetos en el lenguaje java puro usan la nueva memoria asignada en el montón), pero no puede borrar la memoria asignada en la pila (cuando se usa la tecnología JNI, la memoria se puede asignar en la pila, Por ejemplo, cuando java llama al programa c, y el programa c usa malloc para asignar memoria). Por lo tanto, si algunos objetos se asignan en el área de memoria de la pila, entonces gc está fuera de control y la recuperación de la memoria de los objetos de la pila depende de finalize (). Por ejemplo, cuando java llama a un método que no es de Java (este método puede ser c o c ++), la función malloc () de c se puede llamar dentro del código que no es de Java para asignar memoria, y a menos que se llame a free () De lo contrario , la memoria no se liberará (porque free () es una función de c). En este momento, se debe realizar el trabajo de liberar la memoria. GC no funciona, por lo que es necesario llamar a free () en un inherente método dentro de finalize ().

Excelentes hábitos de programación

(1) Evite crear un objeto en el cuerpo del bucle, incluso si el objeto no ocupa mucho espacio en la memoria.
(2) Intente hacer que el objeto cumpla con el estándar de recolección de basura a tiempo.
(3) No adopte una jerarquía de herencia demasiado profunda.
(4) Acceder a las variables locales es mejor que acceder a las variables de la clase.

¡Esta sección se actualizará continuamente!

Tres problemas comunes

1. Desbordamiento de la memoria

Es decir, la memoria de la máquina virtual Java que solicitó asignar excede lo que el sistema puede brindarle y el sistema no puede satisfacer la demanda, por lo que se produce un desbordamiento.
2. Pérdida de memoria

Significa que solicita al sistema que asigne memoria para su uso (nueva), pero no la devuelve (borra) después de usarla. Como resultado, ya no podrá acceder a la memoria que solicitó ni a la memoria asignada. la memoria ya no se puede utilizar Uso, con el consumo continuo de memoria del servidor, y cada vez más memoria inutilizable, el sistema no puede volver a asignarla al programa requerido, lo que resulta en fugas. A medida que continúa, el programa se queda sin memoria gradualmente y se desborda.

El contenido de este capítulo se basa en la teoría. Continuaré agregando algunas operaciones prácticas, como verificar el efecto de la recolección de basura o el monitoreo de la memoria. Al mismo tiempo, espero que los lectores continúen brindando orientación y sugerencias. Si tiene alguna pregunta, comuníquese con: huevo:

Correo electrónico: [email protected]

Weibo: weibo.com/xtfggef

Si se reimprime, indique la fuente ( http://blog.csdn.net/zhangerqing ), ¡gracias!

El fin

Supongo que te gusta

Origin blog.csdn.net/dypnlw/article/details/82687447
Recomendado
Clasificación