Primeros pasos con la recolección de basura

Introducción

Los objetos se vuelven elegibles para la recolección de elementos no utilizados (GC) si ya no son accesibles por los puntos de entrada principales en un programa. Por lo general, el usuario no realiza GC explícitamente, pero para que el GC sepa que un objeto ya no es necesario, un desarrollador puede:

Desreferenciar/asignar nulo

someFunction {
     var a = 1;
     var b = 2;
     a = null; // GC can now free the memory used for variable a
     ...
} // local variable b not dereferenced but will be subject to GC when function ends

Usar referencias débiles

La mayoría de los lenguajes con GC le permiten crear referencias débiles a un objeto que no cuenta como referencia para el GC. Si solo hay referencias débiles a un objeto y ninguna referencia fuerte (normal), entonces el objeto es elegible para GC.

WeakReference wr = new WeakReference(createSomeObject());

Tenga en cuenta que después de este código es peligroso usar el objetivo de la referencia débil sin verificar si el objeto aún existe. Los programadores principiantes a veces cometen el error de usar un código como este:

if wr.target is not null {
    doSomeAction(wr.target);
}

Esto puede causar problemas porque es posible que se haya invocado a GC después de la comprobación nula y antes de la ejecución de doSomeAction. Es mejor crear primero una referencia fuerte (temporal) al objeto como esta:

Object strongRef = wr.target;
if strongRef is not null {
    doSomeAction(strongRef);
}
strongRef = null; 

Habilitación del registro detallado de gc en Java

Normalmente, la recolección de basura (gc) de jvm es transparente para el usuario (desarrollador/ingeniero).

Por lo general, no se requiere el ajuste de GC a menos que el usuario se enfrente a una pérdida de memoria o tenga una aplicación que requiera una gran cantidad de memoria, lo que eventualmente conduce a una excepción de falta de memoria que obliga al usuario a investigar el problema.

El primer paso suele ser aumentar la memoria (ya sea el montón o el perm-gen/meta-space dependiendo de si se debe a la carga en tiempo de ejecución o si la base de la biblioteca de la aplicación es grande o si hay una fuga en la carga de clases o en el subproceso -mecanismo de manipulación). Pero siempre que eso no sea factible, el siguiente paso es tratar de comprender qué es lo que está fallando.

Si uno quiere solo la instantánea en un instante particular en el tiempo, entonces la utilidad jstat que es parte de jdk sería suficiente.

Sin embargo, para una comprensión más detallada, es útil tener un registro que contenga la instantánea del montón antes y después de cada evento gc. Para eso, el usuario debe habilitar el registro detallado de gc utilizando -verbose:gc como parte de los parámetros de inicio de jvm e incluyendo los indicadores -XX:+PrintGCDetails y -XX:+PrintGCTimeStamp.

Para aquellos que deseen generar un perfil proactivo de su aplicación, también existen herramientas como jvisualvm que también es parte del jdk a través del cual pueden obtener información sobre el comportamiento de las aplicaciones.

A continuación se muestra un programa de muestra, la configuración de gc y la salida de registro verbose-gc:

package com.example.so.docs.gc.logging;

import java.util.Arrays;
import java.util.Random;

public class HelloWorld {

    public static void main(String[] args) {
        sortTest();
    }
    
    private static void sortTest() {
        System.out.println("HelloWorld");
        
        int count = 3;
        while(count-- > 0) {
            int size = 1024*1024;
            int[] numbers = new int[size];
            Random random = new Random();
            for(int i=0;i<size;i++) {
                numbers[i] = random.nextInt(size);
            }
            
            Arrays.sort(numbers);
        }
        System.out.println("Done");
        
    }
    

}

Opciones de GC:

-server -verbose:gc  -XX:+PrintGCDetails -XX:+PrintGCTimeStamps  -Xmx10m  -XX:-PrintTenuringDistribution  -XX:MaxGCPauseMillis=250 -Xloggc:/path/to/logs/verbose_gc.log

Producción :

Java HotSpot(TM) 64-Bit Server VM (25.72-b15) for windows-amd64 JRE (1.8.0_72-b15), built on Dec 22 2015 19:16:16 by "java_re" with MS VC++ 10.0 (VS2010)
Memory: 4k page, physical 6084464k(2584100k free), swap 8130628k(3993460k free)
CommandLine flags: -XX:InitialHeapSize=10485760 -XX:MaxGCPauseMillis=250 -XX:MaxHeapSize=10485760 -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:-PrintTenuringDistribution -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC 
0.398: [GC (Allocation Failure) [PSYoungGen: 483K->432K(2560K)] 4579K->4536K(9728K), 0.0012569 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
0.400: [GC (Allocation Failure) [PSYoungGen: 432K->336K(2560K)] 4536K->4440K(9728K), 0.0008121 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
0.401: [Full GC (Allocation Failure) [PSYoungGen: 336K->0K(2560K)] [ParOldGen: 4104K->294K(5632K)] 4440K->294K(8192K), [Metaspace: 2616K->2616K(1056768K)], 0.0056202 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
0.555: [GC (Allocation Failure) [PSYoungGen: 41K->0K(2560K)] 4431K->4390K(9728K), 0.0004678 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
0.555: [GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] 4390K->4390K(9728K), 0.0003490 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
0.556: [Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] [ParOldGen: 4390K->293K(5632K)] 4390K->293K(8192K), [Metaspace: 2619K->2619K(1056768K)], 0.0060187 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
Heap
 PSYoungGen      total 2560K, used 82K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
  eden space 2048K, 4% used [0x00000000ffd00000,0x00000000ffd14938,0x00000000fff00000)
  from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
  to   space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
 ParOldGen       total 5632K, used 4389K [0x00000000ff600000, 0x00000000ffb80000, 0x00000000ffd00000)
  object space 5632K, 77% used [0x00000000ff600000,0x00000000ffa49670,0x00000000ffb80000)
 Metaspace       used 2625K, capacity 4486K, committed 4864K, reserved 1056768K
  class space    used 282K, capacity 386K, committed 512K, reserved 1048576K

A continuación hay algunos enlaces útiles sobre GC:

  1. Una página archivada que explica los conceptos de gc (jdk7)
  2. Tutorial de coleccionista G1
  3. Opciones de máquina virtual útiles
  4. JDK 5 - Ergonomía de GC (los conceptos siguen siendo relevantes)
  5. Tuning de JDK 6 (los conceptos siguen siendo relevantes)