2009-10-28 14 views
7

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

Finalmente, estoy pensando en hacer de este objeto un referente de java.lang.ref.SoftReference. Entonces, si el sistema tiene poca memoria libre, dejará que el objeto se vaya y se creará uno nuevo bajo demanda. Esto disminuiría un poco la velocidad después del nuevo arranque, pero esta es una alternativa mucho mejor que golpear OOM.

El único problema que veo sobre SoftReferences es que no hay una forma clara de obtener sus referentes finalizados. 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 soltarlo. Pero bueno, cada artículo de este planeta desaconseja el uso de finalizadores, y especialmente - contra los finalizadores para liberar manejadores 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 alguna otra alternativa viable? Gracias por adelantado.

EDIT # 1: El texto a continuación se agregó después de probar algún código como lo sugirió Tom Hawtin. Para mí, parece que cualquiera de las sugerencias 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 corro el fragmento anterior con -Xmx10m y SoftReferences (como en el código anterior), estoy recibiendo toneladas de is null :( impresos. Pero si reemplazo el código con MyReference (quitando dos líneas con MyReference y comentándolas con SoftReference) siempre obtengo OOM.

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

Respuesta

5

Toms respuesta es la correcta, sin embargo, el código que se ha añadido a la pregunta no es lo mismo que lo propuesto por Tom.Lo que Tom estaba proponiendo miradas de la misma familia:

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 más difícil es el objeto que necesita ser cerrado. El objeto circundante puede ser, y será, recolectado como basura, por lo que no golpearás al OOM, sin embargo, aún tienes la oportunidad de cerrar la referencia. Una vez que salga del ciclo, también será basura recolectada. Por supuesto, en el mundo real, probablemente no haga que res sea un miembro público de instancias.

Dicho esto, si tiene referencias abiertas de archivos, corre un riesgo muy real de quedarse sin ellas antes de que se quede sin memoria. Probablemente también desee tener un caché LRU para asegurarse de no guardar más de con los dedos en el aire 500 archivos abiertos. También pueden ser de 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, todavía contiene la referencia al objeto que está acaparando toda la memoria. Este es el objeto que debe liberarse para evitar que ocurra el OOM. Sin embargo, si el objeto se libera, aún necesita liberar los recursos que usa Bloat, es decir, que Bloat usa dos tipos de recursos, memoria y un manejador de archivo, ambos recursos deben liberarse, o ejecutar de uno u otro de los recursos. SoftReference maneja la presión en el recurso de memoria liberando ese objeto, sin embargo, también necesita liberar el otro recurso, el manejador de archivo. Debido a que Bloat ya se ha liberado, no podemos utilizarlo para liberar el recurso relacionado, por lo que MySoftBloatReference guarda una referencia difícil al recurso interno que debe cerrarse. Una vez que se ha informado que Bloat se ha liberado, es decir, una vez que aparece la referencia en ReferenceQueue, entonces MySoftBloatReference también puede cerrar el recurso relacionado, a través de la referencia física que tiene.

EDIT: Actualizado el código para que se recopila cuando se lanza en una clase. Utiliza un StringReader para ilustrar el concepto de cómo cerrar el Reader, que se está utilizando para representar el recurso externo que debe liberarse. En este caso particular, el cierre de ese flujo es efectivamente no operativo, por lo que no es necesario, pero muestra cómo hacerlo si es necesario.

+0

¿sería posible fijar su código por lo que compila? Ej MiReferencia constructor toma el argumento referente Inflar y se supone que asignarlo a hardRef, pero hardRef es de un tipo totalmente diferente (ResourceThatMustBeClosed). además, se puede elaborar en qué Inflar sigue siendo necesaria una vez que tenemos ResourceThatMustBeClosed PS no sería tan necesitados si esta pregunta no tenía puntos de bonificación que se adjuntan:? P – mindas

+0

he actualizado el código, y (con suerte) añadido una clara explicación de cómo funciona? Si no es así, que me haga saber ... –

+0

Código se fija para que se compila. Sólo un tiro en una clase vacía, y añadir a la adecuada importaciones. –

7

Para obtener una cantidad limitada de recursos: Subclase SoftReference. La referencia suave debe apuntar al objeto circundante. Una referencia fuerte en la subclase debe hacer referencia al recurso, por lo que siempre es muy accesible. Cuando se lee a través del ReferenceQueuepoll, el recurso se puede cerrar y eliminar de la caché. El caché debe liberarse correctamente (si un SoftReference en sí mismo es basura recogida, no se puede poner en un ReferenceQueue).

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

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

+1

Slick. 15 15 15 15 15. –

+1

Me sorprende que fuera remotamente coherente (¿verdad?) Después de una hora más o menos de sueño, un día en una habitación oscura (con un servicio pobre de café aunque funcionaba wifi) e intentaba escuchar un altavoz . Pero tenía que hacerse. –

+0

Tom, ¿podría publicar (o editar ésta) una respuesta más detallada, eventualmente acompañada de algún (pseudo) código? También tuve un día difícil, tal vez mañana lo entenderé mejor, pero ahora mismo, desafortunadamente no parezco poder hacerlo. –

2

Ahm.
(Hasta donde yo sé) No puedes sostener el stick por los dos extremos. O retienes tu información o la dejas pasar.
Sin embargo ... puede retener información clave que le permitiría finalizar. Por supuesto, la información clave debe ser significativamente más pequeña 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).
Basándose en el ejemplo existente (prestar atención al campo de la 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 "bien mantener su información o dejar que se vaya". Suponiendo que tuvieras alguna manera de mantener tu información. Eso habría forzado al GC a desmarcar sus datos, causando que los datos realmente 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 finaliza(). Como usted indicó que no desea que ocurra el segundo ciclo, no puede retener su información (si a -> b luego! B ->! A). lo que significa que debes dejarlo ir.

Edit2:
En realidad, se producirí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.

Edit3:
Obviamente, la verdadera solución sería utilizar un hilo separado para la eliminación de la cola de referencia (no poll(), remove(), el bloqueo en el hilo dedicado).

+0

se olvidó de mencionar - ejecutar este ejemplo con 10mb -Xmx no produce OOM y la lista hace todo tipo de números (que se supone "información clave"). –

0

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

@Ran - Creo que en su código actual falta i ++ al final del ciclo. Además, no es necesario que haga rq.remove() en el bucle ya que rq.poll() ya elimina la referencia superior, ¿no es así?

pocos puntos:

1) he tenido que añadir Thread.sleep (1) declaración después de i ++ en el bucle (para ambas soluciones de Pablo y Ran) para evitar OOM pero eso es irrelevante para el cuadro grande y es también depende de la plataforma. Mi máquina tiene una CPU de cuatro núcleos y está ejecutando Sun Linux 1.6.0_16 JDK.

2) Después de ver estas soluciones, creo que me quedaré con los finalizadores. El libro de Bloch ofrece siguientes razones:

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

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

3) Tristemente, no hay forma de dividir puntos entre Paul, Tom y Ran :( Espero que a Tom no le importe ya que ya tiene muchos de ellos :) Juzgar entre Paul y Ran fue mucho más difícil - Creo que ambas respuestas funcionan y son correctas. Solo estoy configurando aceptar bandera en la respuesta de Paul porque fue calificada como más alta (y tiene una explicación más detallada), pero la solución de Ran no está nada mal y probablemente sería mi elección si decidiera implementarla usando SoftReferences. ¡Gracias chicos!

+0

i ++ - sí, probablemente no pasó por la copia/pega. sin necesidad de eliminar() - correcto. Me falta la mitad de las referencias. –

Cuestiones relacionadas