Pregunta

Estoy usando una biblioteca de búsqueda que aconseja mantener abierto el objeto del controlador de búsqueda para esto puede beneficiar la caché de consultas. Con el tiempo he observado que el caché tiende a hincharse (unos pocos cientos de megas y sigue creciendo) y los OOM comenzaron a funcionar. No hay forma de imponer los límites de este caché ni planificar cuánta memoria puede usar. Así que he aumentado el límite de Xmx , pero eso es solo una solución temporal al problema.

Eventualmente estoy pensando en hacer de este objeto un referente de java.lang.ref.SoftReference . Entonces, si el sistema se queda sin memoria libre, dejaría ir el objeto y se crearía uno nuevo bajo demanda. Esto disminuiría algo de velocidad después de un nuevo comienzo, pero esta es una alternativa mucho mejor que presionar OOM.

El único problema que veo sobre SoftReferences es que no hay una manera limpia de finalizar sus referentes. En mi caso, antes de destruir el identificador de búsqueda necesito cerrarlo, de lo contrario el sistema podría quedarse sin descriptores de archivo. Obviamente, puedo envolver este controlador en otro objeto, escribir un finalizador en él (o engancharlo en una ReferenceQueue / PhantomReference) y dejarlo ir. Pero bueno, cada artículo en este planeta desaconseja el uso de finalizadores y, especialmente, contra los finalizadores para liberar identificadores de archivos (por ejemplo, Java efectivo ed. II, página 27).

Así que estoy algo perplejo. Debería ignorar cuidadosamente todos estos consejos y continuar. De lo contrario, ¿hay otras alternativas viables? Gracias de antemano.

EDITAR # 1: El texto a continuación se agregó después de probar un código sugerido por Tom Hawtin. Para mí, parece que o la sugerencia no funciona o me falta algo. Aquí está el código:

class Bloat {  // just a heap filler really
   private double a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z;

   private final int ii;

   public Bloat(final int ii) {
      this.ii = ii;
   }
}

// as recommended by Tom Hawtin
class MyReference<T> extends SoftReference<T> {
   private final T hardRef;

   MyReference(T referent, ReferenceQueue<? super T> q) {
      super(referent, q);
      this.hardRef = referent;
   }
}

//...meanwhile, somewhere in the neighbouring galaxy...
{
   ReferenceQueue<Bloat> rq = new ReferenceQueue<Bloat>();
   Set<SoftReference<Bloat>> set = new HashSet<SoftReference<Bloat>>();
   int i=0;

   while(i<50000) {
//      set.add(new MyReference<Bloat>(new Bloat(i), rq));
      set.add(new SoftReference<Bloat>(new Bloat(i), rq));

//      MyReference<Bloat> polled = (MyReference<Bloat>) rq.poll();
      SoftReference<Bloat> polled = (SoftReference<Bloat>) rq.poll();

      if (polled != null) {
         Bloat polledBloat = polled.get();
         if (polledBloat == null) {
           System.out.println("is null :(");
         } else {
           System.out.println("is not null!");
         }
      }
      i++;
   }
}

Si ejecuto el fragmento anterior con -Xmx10m y SoftReferences (como en el código anterior), obtengo toneladas de es nulo :( impreso. Pero si yo reemplace el código con MyReference (descomentando dos líneas con MyReference y comentando las que tienen SoftReference) Siempre obtengo OOM.

Como entendí por el consejo, tener una referencia dura dentro de MyReference no debería evitar que el objeto golpee ReferenceQueue , ¿verdad?

¿Fue útil?

Solución

La respuesta de Toms es la correcta, sin embargo, el código que se ha agregado a la pregunta no es el mismo que el propuesto por Tom. Lo que Tom estaba proponiendo se parece más a esto:

class Bloat {  // just a heap filler really
    public Reader res;
    private double a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z;

    private final int ii;

    public Bloat(final int ii, Reader res) {
       this.ii = ii;
       this.res = res;
    }
 }

 // as recommended by Tom Hawtin
 class MySoftBloatReference extends SoftReference<Bloat> {
    public final Reader hardRef;

    MySoftBloatReference(Bloat referent, ReferenceQueue<Bloat> q) {
       super(referent, q);
       this.hardRef = referent.res;
    }
 }

 //...meanwhile, somewhere in the neighbouring galaxy...
 {
    ReferenceQueue<Bloat> rq = new ReferenceQueue<Bloat>();
    Set<SoftReference<Bloat>> set = new HashSet<SoftReference<Bloat>>();
    int i=0;

    while(i<50000) {
        set.add(new MySoftBloatReference(new Bloat(i, new StringReader("test")), rq));

        MySoftBloatReference polled = (MySoftBloatReference) rq.poll();

        if (polled != null) {
            // close the reference that we are holding on to
            try {
                polled.hardRef.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        i++;
    }
}

Tenga en cuenta que la gran diferencia es que la referencia dura es al objeto que debe cerrarse. El objeto circundante puede, y será, recolectar basura, por lo que no golpeará el OOM, sin embargo, aún tendrá la oportunidad de cerrar la referencia. Una vez que abandonas el ciclo, también se recogerá la basura. Por supuesto, en el mundo real, probablemente no haría de res un miembro de instancia pública.

Dicho esto, si tiene referencias de archivos abiertos, corre un riesgo muy real de quedarse sin ellas antes de quedarse sin memoria. Probablemente también desee tener un caché LRU para asegurarse de no mantener más de palos de dedo en el aire 500 archivos abiertos. Estos también pueden ser del tipo MyReference para que también se puedan recolectar basura si es necesario.

Para aclarar un poco sobre cómo funciona MySoftBloatReference, la clase base, que es SoftReference, aún conserva la referencia al objeto que está acaparando toda la memoria. Este es el objeto que necesita liberar para evitar que ocurra el OOM. Sin embargo, si el objeto se libera, aún necesita liberar los recursos que Bloat está utilizando, es decir, Bloat está utilizando dos tipos de recursos, memoria y un identificador de archivo, ambos recursos deben liberarse o ejecutar fuera de uno u otro de los recursos. SoftReference maneja la presión sobre el recurso de memoria al liberar ese objeto, sin embargo, también debe liberar el otro recurso, el identificador de archivo. Debido a que Bloat ya se ha liberado, no podemos usarlo para liberar el recurso relacionado, por lo que MySoftBloatReference mantiene una referencia sólida al recurso interno que debe cerrarse. Una vez que se ha informado que el Bloat se ha liberado, es decir, una vez que la referencia aparece en ReferenceQueue, MySoftBloatReference también puede cerrar el recurso relacionado, a través de la referencia física que tiene.

EDITAR: actualizó el código para que se compile cuando se lanza a una clase. Utiliza un StringReader para ilustrar el concepto de cómo cerrar el Reader, que se utiliza para representar el recurso externo que necesita ser liberado. En este caso particular, cerrar ese flujo es efectivamente no operativo, por lo que no es necesario, pero muestra cómo hacerlo si es necesario.

Otros consejos

Para un número finito de recursos: Subclase SoftReference . La referencia suave debe apuntar al objeto que lo encierra. Una referencia fuerte en la subclase debe hacer referencia al recurso, por lo que siempre es muy accesible. Cuando se lee el ReferenceQueue poll , el recurso se puede cerrar y eliminar de la memoria caché. El caché debe liberarse correctamente (si un SoftReference es basura recolectada, no se puede poner en cola en un ReferenceQueue ).

Tenga cuidado de que solo tenga un número finito de recursos no liberados en la memoria caché: desaloje las entradas antiguas (de hecho, puede descartar las referencias suaves con si la memoria caché finita, si eso se adapta a su situación). Por lo general, es el recurso sin memoria el más importante, en cuyo caso un caché de desalojo de LRU sin objetos de referencia exóticos debería ser suficiente.

(Mi respuesta # 1000. Publicado desde London DevDay.)

Ahm.
(Que yo sepa) No puedes sostener el palo por ambos extremos. O mantienes tu información o la dejas ir.
Sin embargo ... puede conservar alguna información clave que le permita finalizar. Por supuesto, la información clave debe ser significativamente menor que la "información real". y no debe tener la información real en su gráfico de objeto accesible (las referencias débiles pueden ayudarlo allí).
Partiendo del ejemplo existente (preste atención al campo de información clave):

public class Test1 {
    static class Bloat {  // just a heap filler really
        private double a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z;

        private final int ii;

        public Bloat(final int ii) {
            this.ii = ii;
        }
    }

    // as recommended by Tom Hawtin
    static class MyReference<T, K> extends SoftReference<T> {
        private final K keyInformation;

        MyReference(T referent, K keyInformation, ReferenceQueue<? super T> q) {
            super(referent, q);
            this.keyInformation = keyInformation;
        }

        public K getKeyInformation() {
            return keyInformation;
        }
    }

    //...meanwhile, somewhere in the neighbouring galaxy...
    public static void main(String[] args) throws InterruptedException {
        ReferenceQueue<Bloat> rq = new ReferenceQueue<Bloat>();
        Set<SoftReference<Bloat>> set = new HashSet<SoftReference<Bloat>>();
        int i = 0;

        while (i < 50000) {
            set.add(new MyReference<Bloat, Integer>(new Bloat(i), i, rq));

            final Reference<? extends Bloat> polled = rq.poll();

            if (polled != null) {
                if (polled instanceof MyReference) {
                    final Object keyInfo = ((MyReference) polled).getKeyInformation();
                    System.out.println("not null, got key info: " + keyInfo + ", finalizing...");
                } else {
                    System.out.println("null, can't finalize.");
                }
                rq.remove();
                System.out.println("removed reference");
            }

Editar:
Quiero dar más detalles sobre el " ya sea mantener su información o dejarla ir " ;. Suponiendo que tuviera alguna forma de mantener su información. Eso habría obligado al GC a desmarcar sus datos, haciendo que los datos se limpien solo después de que haya terminado con ellos, en un segundo ciclo de GC. Esto es posible, y es exactamente para lo que finalize () es. Como usted declaró que no desea que ocurra el segundo ciclo, no puede guardar su información (si a - > b entonces! B - >! A). lo que significa que debes dejarlo ir.

Editar2:
En realidad, ocurriría un segundo ciclo, pero para sus " datos clave " ;, no sus " datos principales de hinchazón " ;. Los datos reales se borrarán en el primer ciclo.

Editar3:
Obviamente, la solución real usaría un hilo separado para eliminar de la cola de referencia (no sondear (), eliminar (), bloquear el hilo dedicado).

@Paul - muchas gracias por la respuesta y aclaración.

@Ran: creo que en su código actual falta i ++ al final del ciclo. Además, no necesita hacer rq.remove () en el bucle ya que rq.poll () ya elimina la referencia superior, ¿no?

Pocos puntos:

1) Tuve que agregar la declaración Thread.sleep (1) después de i ++ en el bucle (para ambas soluciones de Paul y Ran) para evitar OOM, pero eso es irrelevante para el panorama general y también depende de la plataforma. Mi máquina tiene una CPU de cuatro núcleos y ejecuta Sun Linux 1.6.0_16 JDK.

2) Después de mirar estas soluciones, creo que me quedaré usando finalizadores. El libro de Bloch proporciona las siguientes razones:

  • no hay garantías de que los finalizadores se ejecutarán rápidamente, por lo tanto, nunca haga nada crítico en un finalizador, ¡ni hay garantías para SoftRererences!
  • Nunca dependa de un finalizador para actualizar el estado crítico persistente - No estoy
  • hay una severa penalización de rendimiento por usar finalizadores: en el peor de los casos, estaría finalizando sobre un solo objeto por minuto más o menos. Creo que puedo vivir con eso.
  • usa try / finally - ¡oh sí, definitivamente lo haré!

Tener la necesidad de crear una gran cantidad de andamios solo para lo que parece una tarea simple no me parece razonable. Quiero decir, literalmente, la tasa de WTF por minuto sería bastante alta para cualquier otra persona que esté mirando dicho código.

3) Lamentablemente, no hay forma de dividir puntos entre Paul, Tom y Ran :( Espero que a Tom no le importe ya que ya tiene muchos :) Juzgar entre Paul y Ran fue mucho más difícil: creo que ambas respuestas funcionan y son correctas. Solo configuro el indicador de aceptación para la respuesta de Paul porque se calificó más alto (y tiene una explicación más detallada), pero la solución de Ran no es mala en absoluto y probablemente sería mi elección si optara por implementarla usando SoftReferences. ¡Gracias chicos!

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top