¿Cómo forzar la recolección de basura en Java?

225

¿Es posible forzar la recolección de basura en Java, incluso si es difícil de hacer? Lo sé System.gc();y Runtime.gc();solo sugieren hacer GC. ¿Cómo puedo forzar GC?

Robert Columbia
fuente
30
Quizás sería útil proporcionar algunos antecedentes de por qué necesita forzar GC. Por lo general, en un lenguaje de recolección de basura, es una mala práctica llamar explícitamente al recolector.
Justin Ethier
3
Una JVM dada puede proporcionar varios métodos de recolección de basura, cada uno con sus propias ventajas y desventajas, y con frecuencia se puede evitar una situación dada simplemente insinuando la JVM en el momento del inicio. Por favor, explique el escenario.
Thorbjørn Ravn Andersen
3
jmap -histo: live <pid> stackoverflow.com/questions/6418089/…
55
Aquí hay un caso de uso para forzar la recolección de basura: tengo un servidor con un montón de 30 GB, de los cuales ~ 12 GB se usan típicamente (~ 5 millones de objetos). Cada 5 minutos, el servidor dedica aproximadamente un minuto a realizar una tarea compleja en la que se utilizan aproximadamente 35 millones de objetos adicionales. Un GC completo se activa un par de veces por hora, invariablemente durante la tarea compleja, y congela la VM durante 10 a 15 segundos. Me encantaría forzar la ejecución del GC completo en un momento en que la tarea compleja no se está ejecutando; entonces estaría haciendo malabares con 5 millones de objetos vivos en lugar de 40 millones.
Steve
3
@JustinEthier Hay un caso bastante obvio en el que es posible que desee forzar el GC, que es la unidad que prueba cualquier comportamiento que involucre la jerarquía de tipos java.lang.ref.Reference.
Elias Vasylenko

Respuestas:

168

Su mejor opción es llamar, lo System.gc()que simplemente es una pista para el recolector de basura que desea que haga una recolección. Sin embargo, no hay forma de forzar una recolección inmediata ya que el recolector de basura no es determinista.

Andrew Hare
fuente
28
Debería haber. non-deterministic == trouble
Pacerier
77
Un recolector de basura puede ser no determinista y aun así ofrecer una forma de forzar una recolección inmediata. Por ejemplo, por lo general, el recopilador .NET no es determinista, pero una llamada a GC.Collect () lo obliga a ejecutarse. Es solo que Java elige no exponer esta función.
Petr Hudeček
2
En mi experiencia, este método siempre invoca al recolector de basura. Lo hace con suficiente regularidad que mis parcelas de uso de memoria frente al número de objetos declarados son siempre estrictamente lineales (que representan el relleno, etc.).
Jim Pivarski
Estaba pensando que al asignar nuevos objetos y luego no hacer referencia a ellos nunca más, el recolector de basura se ejecutaría automáticamente
Bionix1441
@ PetrHudeček En aplicaciones del mundo real .NET GC.Collect()no recopila. En Java gc()sí.
ajeh
53

La biblioteca jlibs tiene una buena clase de utilidad para la recolección de basura . Puede forzar la recolección de basura utilizando un ingenioso truco con objetos WeakReference .

RuntimeUtil.gc () de jlibs:

   /**
    * This method guarantees that garbage collection is
    * done unlike <code>{@link System#gc()}</code>
    */
   public static void gc() {
     Object obj = new Object();
     WeakReference ref = new WeakReference<Object>(obj);
     obj = null;
     while(ref.get() != null) {
       System.gc();
     }
   }
farsa
fuente
1
Este código se rompe porque una referencia débil se borra tan pronto como su referente se vuelve débilmente accesible, es decir, antes de que se borre de la memoria.
Marko Topolnik
1
Puede estar combinando el significado de "GC se ha ejecutado" con "se ha recuperado la memoria". El objeto sigue vivo y aún no está finalizado, pero ya no puedes acceder a él a través de la referencia débil. Una forma algo mejor sería usar un PhantomReferencecon ay ReferenceQueueluego recibiría una notificación después de la finalización, pero aún antes de la limpieza. Finalmente, incluso si detecta con éxito que se recuperó la memoria para este objeto, todavía significaría muy poco en un GC generacional como HotSpot's. Por lo general, coincidiría con la limpieza de la generación joven.
Marko Topolnik
20
OP solicitó, y usted afirmó proporcionar, una solución para "forzar la recolección de basura". Ejecutar el subsistema GC es una cosa, en realidad recolectar basura es otra. El ejemplo de código que proporcionó tiene claramente la intención de garantizar que se haya recolectado la basura. De todos modos, esta es una pregunta muy antigua, obviamente no se trata de los deseos de OP, sino de la utilidad para el público en general. Nadie está interesado en "obligar al subsistema GC a ejecutarse" por sí solo, sin recolectar basura. De hecho, la gente generalmente quiere una garantía de que toda la basura ha sido recolectada.
Marko Topolnik
44
Probablemente no lo haya comparado con la eficiencia de System.gc(); System.gc();, pero seguramente sería interesante saber si alguna vez funcionó mejor que eso. De hecho, solo imprimir cuántas veces fue System.gc()suficiente. La posibilidad de llegar a 2 es bastante escasa.
Marko Topolnik
3
@MarkoTopolnik: "Nadie está interesado en" obligar al subsistema GC a ejecutarse "por sí solo, sin recolectar basura" ... En realidad, hoy estaba interesado solo en este comportamiento. Estoy agradecido de que esta respuesta estuviera presente. Mi propósito era verificar el comportamiento rotacional del manejo del registro del GC y obtener el formato de la salida del GC. Este pequeño truco me ayudó a llenar los registros de GC rápidamente.
erik.weathers
49

La mejor (si no solo) forma de forzar un GC sería escribir una JVM personalizada. Creo que los recolectores de basura son conectables, por lo que probablemente podría elegir una de las implementaciones disponibles y ajustarla.

Nota: Esta NO es una respuesta fácil.

Mainguy
fuente
40
+1 para lulz. nada hace la depuración frustrante mejor que alguien con sentido del humor. aparte de una respuesta realmente utilizable, es decir.
jsh
25

, es casi posible forzarlo, debe llamar a los métodos en el mismo orden y al mismo tiempo estos son:

System.gc ();
System.runFinalization ();

incluso si es solo un objeto para limpiar el uso de estos dos métodos al mismo tiempo, obligue al recolector de basura a usar el finalise()método de objeto inalcanzable, liberando la memoria asignada y haciendo lo que dice el finalize()método.

SIN EMBARGO , es una práctica terrible usar el recolector de basura porque su uso podría introducir una sobrecarga en el software que puede ser incluso peor que en la memoria, el recolector de basura tiene su propio hilo que no es posible controlar más dependiendo de el algoritmo utilizado por el gc podría tomar más tiempo y se considera muy ineficiente, debe verificar su software si es peor con la ayuda del gc porque definitivamente está roto, una buena solución no debe depender del gc.

NOTA: solo para tener en cuenta que esto funcionará solo si en el método de finalización no hay una reasignación del objeto, si esto sucede, el objeto se mantendrá vivo y tendrá una resurrección que es técnicamente posible.

legramira
fuente
10
NO , incluso estos dos comandos NO forzarán una recolección de basura. Como ya mencionaron otros, gc()es solo una pista para ejecutar una recolección de basura. runFinalizers()solo ejecuta finalizadores en objetos "que han sido descartados". Si el gc no se ejecutó realmente, puede que no haya tales objetos ...
Steffen Heil
Además, System.runFinalization () no es una garantía de que algo se ejecutará; es posible que no pase nada en absoluto. Es una sugerencia , de Javadoc: " Llamar a este método sugiere que la máquina virtual Java haga un esfuerzo para ejecutar los métodos de finalización de los objetos que se han descartado pero cuyos métodos de finalización aún no se han ejecutado "
kaan
21

Según la documentación de OutOfMemoryError , declara que no se lanzará a menos que la VM no haya podido recuperar la memoria después de una recolección de basura completa. Entonces, si sigue asignando memoria hasta que obtenga el error, ya habrá forzado una recolección de basura completa.

Presumiblemente, la pregunta que realmente quería hacer era "¿cómo puedo reclamar el recuerdo que creo que debería reclamar mediante la recolección de basura?"

Pete Kirkham
fuente
18

Para solicitar GC manualmente (no desde System.gc ()):

  1. Vaya a: carpeta bin en JDK, por ejemplo, C: \ Archivos de programa \ Java \ jdk1.6.0_31 \ bin
  2. Abra jconsole.exe
  3. Conéctese al proceso local deseado.
  4. Vaya a la pestaña de memoria y haga clic en realizar GC.
Pinkesh Sharma
fuente
3
Oppsss engañoso. Pase el mouse sobre el botón "Realizar GC". Puede solicitar que JVM realice GC pero nunca forzar.
Kumaran
@PinkeshSharma, esto no fuerza . Es una mera solicitud que probablemente podría ignorarse por completo.
Pacerier
@Pacerier En un mundo ideal, sí ... pero si haces esto, verás que hay un aumento en la memoria al instante ...
Pinkesh Sharma
11

.gc es un candidato para la eliminación en futuras versiones: un ingeniero de Sun comentó una vez que tal vez menos de veinte personas en el mundo realmente saben cómo usar .gc (). Anoche trabajé durante algunas horas en una central estructura de datos que utiliza datos generados por SecureRandom, en algún lugar justo después de 40,000 objetos, el vm se ralentizaría como si se hubiera quedado sin punteros. Claramente, se estaba ahogando en las tablas de punteros de 16 bits y exhibía un comportamiento clásico de "maquinaria defectuosa".

Intenté -Xms y así sucesivamente, seguí jugando un poco hasta que llegó a unos 57, xxx algo. Luego, correría gc pasando de decir 57.127 a 57.128 después de un gc (), aproximadamente al ritmo de hincha de código en el campamento Easy Money.

Su diseño necesita una revisión fundamental, probablemente un enfoque de ventana deslizante.

Nicholas Jordan
fuente
1
Tengo algo así, muchos objetos en la memoria que no puedo desasignarlos. Se produce la excepción OutOfMemory, quiero forzar a GC a probar si hay algún proceso de creación de objetos infinitos o si estos objetos son los utilizados por mi sistema.
Parece que estás trabajando en el mismo problema que yo, explica por favor: "Creación de objetos infinitos" ... buen proyecto de investigación, tal vez puedas publicar una pregunta o algo en un área de Java aquí (soy nuevo aquí y no conozco el "Automa finito" de cómo funciona el sitio) Intenté ayer y terminé haciendo file.dat ya que el compilador se quejó de "demasiado código" en 40,000 bases36 BigIntegers codificados como una Cadena final estática [] Voy a meter el cuello aquí y especulan que toda la JVM está limitada en punteros de 16 bits, apuesto a que lo que tenemos que hacer es agresivamente nulo y leerlo desde el disco ...
Nicholas Jordan
Realmente, no te entiendo. Pero para que quede claro acerca de la "Creación infinita de objetos", quise decir que hay un fragmento de código en mi gran sistema que crea objetos que maneja y está vivo en la memoria, ¡no pude obtener este fragmento de código en realidad, solo un gesto!
55
¡Disparates! Hay un caso obvio en el que debería usarse: probar el código que usa referencias débiles, de modo que podamos asegurarnos de que el comportamiento sea correcto cuando se eliminen las referencias débiles.
Elias Vasylenko
6

La especificación JVM no dice nada específico sobre la recolección de basura. Debido a esto, los vendedores son libres de implementar GC a su manera.

Entonces, esta vaguedad causa incertidumbre en el comportamiento de recolección de basura. Debe verificar los detalles de su JVM para conocer los enfoques / algoritmos de recolección de basura. También hay opciones para personalizar el comportamiento también.

rai.skumar
fuente
4

Si necesita forzar la recolección de basura, tal vez debería considerar cómo administra los recursos. ¿Estás creando objetos grandes que persisten en la memoria? ¿Está creando objetos grandes (p. Ej., Clases de gráficos) que tienen una Disposableinterfaz y no llama dispose()cuando termina con ella? ¿Estás declarando algo a nivel de clase que solo necesitas dentro de un solo método?

Bob Kaufman
fuente
2

Sería mejor si describiera la razón por la que necesita la recolección de basura. Si está utilizando SWT, puede disponer recursos como Imagey Fontpara liberar memoria. Por ejemplo:

Image img = new Image(Display.getDefault(), 16, 16);
img.dispose();

También hay herramientas para determinar recursos no expuestos.

Paul Lammertsma
fuente
¿Qué pasa si no hay ningún método de eliminación?
ArifMustafa
1
Completamente ajeno a la pregunta! No, no estoy usando SWT. Llamo a un método JNI que abre una ventana .NET a través de una capa nativa de Delphi. También tengo un núcleo de cálculo FORTRAN que recibe datos a través de una capa nativa de C ++. ¿Qué tiene eso que ver con esto? ¿Puedo forzar un GC o no? ¿No? :-(
Mostafa Zeinali
0

Si se está quedando sin memoria y está obteniendo una OutOfMemoryException, puede intentar aumentar la cantidad de espacio de almacenamiento dinámico disponible para Java iniciando su programa en java -Xms128m -Xmx512mlugar de solo java. Esto le dará un tamaño de almacenamiento dinámico inicial de 128 Mb y un máximo de 512 Mb, que es mucho más que el estándar de 32 Mb / 128 Mb.

Viktor Dahl
fuente
La configuración de memoria predeterminada esjava -Xms512M -Xmx1024M
ThePyroEagle
0

Otra opción es no crear nuevos objetos.

La agrupación de objetos está lejos para reducir la necesidad de GC en Java.

La agrupación de objetos generalmente no va a ser más rápida que la creación de objetos (especialmente para objetos livianos) pero es más rápida que la recolección de basura. Si creó 10,000 objetos y cada objeto tenía 16 bytes. Eso es 160,000 bytes que GC tiene que reclamar. Por otro lado, si no necesita todos los 10,000 al mismo tiempo, puede crear un grupo para reciclar / reutilizar los objetos, lo que elimina la necesidad de construir nuevos objetos y elimina la necesidad de GC objetos antiguos.

Algo como esto (no probado). Y si desea que sea seguro para subprocesos, puede cambiar la LinkedList por una ConcurrentLinkedQueue.

public abstract class Pool<T> {
    private int mApproximateSize;
    private LinkedList<T> mPool = new LinkedList<>();

    public Pool(int approximateSize) {
        mApproximateSize = approximateSize;
    }

    public T attain() {
        T item = mPool.poll();
        if (item == null) {
            item = newInstance();
        }
        return item;
    }

    public void release(T item) {
        int approxSize = mPool.size(); // not guaranteed accurate
        if (approxSize < mApproximateSize) {
            recycle(item);
            mPool.add(item);
        } else if (approxSize > mApproximateSize) {
            decommission(mPool.poll());
        }
    }

    public abstract T newInstance();

    public abstract void recycle(T item);

    public void decommission(T item) { }

}
Aaron T Harris
fuente
0

En OracleJDK 10 con G1 GC, una sola llamada a System.gc()hará que GC limpie la colección antigua. No estoy seguro si GC se ejecuta de inmediato. Sin embargo, GC no limpiará la Colección Young, incluso si System.gc()se llama muchas veces en un bucle. Para que GC limpie la Young Collection, debe asignar en un bucle (por ejemplo new byte[1024]) sin llamar System.gc(). Llamar System.gc()por alguna razón evita que GC limpie la Young Collection.

Nathan
fuente
0

Realmente, no te entiendo. Pero para que quede claro acerca de la "Creación infinita de objetos", quise decir que hay un fragmento de código en mi gran sistema que crea objetos que maneja y está vivo en la memoria, ¡no pude obtener este fragmento de código en realidad, solo un gesto!

Esto es correcto, solo un gesto. Tiene casi las respuestas estándar ya dadas por varios carteles. Tomemos esto uno por uno:

  1. No pude obtener este código en realidad

Correcto, no existe una jvm real, tal es solo una especificación, un montón de ciencias de la computación que describen un comportamiento deseado ... Hace poco busqué la inicialización de objetos Java a partir de código nativo. Para obtener lo que desea, la única forma es hacer lo que se llama anulación agresiva. Los errores si se hacen mal son tan malos que tenemos que limitarnos al alcance original de la pregunta:

  1. alguna pieza de código en mi gran sistema crea objetos

La mayoría de los carteles aquí supondrán que está diciendo que está trabajando para una interfaz, de ser así, tendríamos que ver si le están entregando el objeto completo o un artículo a la vez.

Si ya no necesita un objeto, puede asignar un valor nulo al objeto, pero si se equivoca, se genera una excepción de puntero nulo. Apuesto a que puedes lograr un mejor trabajo si usas NIO

Cada vez que usted o yo o cualquier otra persona recibe: " Por favor, lo necesito horriblemente " . Es un precursor casi universal de la destrucción casi total de lo que está tratando de trabajar ... escríbanos un pequeño código de muestra, desinfectando cualquier código real utilizado y muéstrenos su pregunta.

No te frustres. A menudo, lo que esto resuelve es que su dba está usando un paquete comprado en algún lugar y el diseño original no está ajustado para estructuras de datos masivas.

Eso es muy comun.

Nicholas Jordan
fuente
-1

FYI

La llamada al método System.runFinalizersOnExit (true) garantiza que se invoquen los métodos finalizadores antes de que Java se cierre. Sin embargo, este método es inherentemente inseguro y ha quedado en desuso. Una alternativa es agregar "ganchos de apagado" con el método Runtime.addShutdownHook.

Masarrat Siddiqui

Masarrat Siddiqui
fuente
El defecto con los ganchos de apagado es que rara vez funcionan. La terminación forzada no funciona, el código de salida distinto de cero no funciona y, a veces, la JVM (oficial) simplemente no los ejecuta mientras los necesite.
ThePyroEagle
-1

Hay alguna forma indirecta de forzar al recolector de basura. Solo necesita llenar el montón con objetos temporales hasta el punto en que se ejecutará el recolector de basura. He hecho una clase que fuerza al recolector de basura de esta manera:

class GarbageCollectorManager {

    private static boolean collectionWasForced;
    private static int refCounter = 0;

    public GarbageCollectorManager() {
        refCounter++;
    }

    @Override
    protected void finalize() {
        try {
            collectionWasForced = true;
            refCounter--;
            super.finalize();   
        } catch (Throwable ex) {
            Logger.getLogger(GarbageCollectorManager.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public int forceGarbageCollection() {
        final int TEMPORARY_ARRAY_SIZE_FOR_GC = 200_000;
        int iterationsUntilCollected = 0;
        collectionWasForced = false;

        if (refCounter < 2) 
            new GarbageCollectorManager();

        while (!collectionWasForced) {
            iterationsUntilCollected++;
            int[] arr = new int[TEMPORARY_ARRAY_SIZE_FOR_GC];
            arr = null;
        }

        return iterationsUntilCollected;
    }

}

Uso:

GarbageCollectorManager manager = new GarbageCollectorManager();
int iterationsUntilGcExecuted = manager.forceGarbageCollection();

No sé cuánto es útil este método, porque llena el montón constantemente, pero si tiene una aplicación de misión crítica que DEBE forzar GC, cuando esta puede ser la forma portátil de Java de forzar GC.

Agnius Vasiliauskas
fuente
¿Qué es "final int TEMPORARY_ARRAY_SIZE_FOR_GC = 200_000;"?
Koray Tugay
Tamaño de matriz: cuántos objetos temporales (int) se generarán para que GC comience a funcionar.
Agnius Vasiliauskas
¿Es válido esto: "_" en un entero?
Koray Tugay
1
Sí, los guiones bajos en literales numéricos son válidos a partir de Java SE 7. Esto es útil, por ejemplo, como separador de miles en enteros como en este caso.
Agnius Vasiliauskas
3
Usted debe no ejecutar dicho código en un sistema de producción. Si bien este código se ejecuta en un subproceso, cualquier otro subproceso también puede obtener una excepción OutOfMemoryException, revierte por completo la intención de llamar a esto en primer lugar ...
Steffen Heil
-1

Me gustaría agregar algo aquí. Tenga en cuenta que Java no se ejecuta en una máquina virtual y no en una máquina real. La máquina virtual tiene su propia forma de comunicación con la máquina. Puede variar de un sistema a otro. Ahora, cuando llamamos al GC, le pedimos a la máquina virtual de Java que llame al recolector de basura.

Dado que el recolector de basura está con la máquina virtual, no podemos obligarlo a hacer una limpieza allí mismo. En lugar de hacer cola nuestra solicitud con el recolector de basura. Depende de la máquina virtual, después de un tiempo particular (esto puede cambiar de un sistema a otro, generalmente cuando la memoria de umbral asignada a la JVM está llena) la máquina real liberará el espacio. :RE

Saubhagya Ranjan Das
fuente
La primera oración del segundo párrafo es un non sequitur .
Marqués de Lorne
-1

El siguiente código está tomado del método afirmarGC (...). Intenta forzar al recolector de basura no determinista a recolectar.

   List<byte[]> alloc = new ArrayList<byte[]>();
   int size = 100000;
   for (int i = 0; i < 50; i++) {
        if (ref.get() == null) {
             // Test succeeded! Week referenced object has been cleared by gc.
             return;
        }
        try {
             System.gc();
        } catch (OutOfMemoryError error) {
             // OK
        }
        try {
             System.runFinalization();
        } catch (OutOfMemoryError error) {
             // OK
        }

        // Approach the jvm maximal allocatable memory threshold
        try {
             // Allocates memory.
             alloc.add(new byte[size]);

             // The amount of allocated memory is increased for the next iteration.
             size = (int)(((double)size) * 1.3);
        } catch (OutOfMemoryError error) {
             // The amount of allocated memory is decreased for the next iteration.
             size = size / 2;
        }

        try {
             if (i % 3 == 0) Thread.sleep(321);
        } catch (InterruptedException t) {
             // ignore
        }
   }

   // Test failed! 

   // Free resources required for testing
   alloc = null;

   // Try to find out who holds the reference.
   String str = null;
   try {
        str = findRefsFromRoot(ref.get(), rootsHint);
   } catch (Exception e) {
        throw new AssertionFailedErrorException(e);
   } catch (OutOfMemoryError err) {
        // OK
   }
   fail(text + ":\n" + str);

Fuente (agregué algunos comentarios para mayor claridad): Ejemplo de NbTestCase

H. Hess
fuente
-1

Puede intentar usar Runtime.getRuntime().gc()o usar el método de utilidad System.gc()Nota: estos métodos no garantizan GC. Y su alcance debe limitarse a JVM en lugar de manejarlo programáticamente en su aplicación.

nabster
fuente
2
Como se explica en las otras respuestas, esos métodos no fuerzan una ejecución (completa) de recolección de basura.
Flujo
-2

Si está utilizando JUnit y Spring, intente agregar esto en cada clase de prueba:

@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
Aliuk
fuente