2010-02-01 36 views
14

Estoy trabajando en un servidor Java que maneja mucho tráfico muy denso. El servidor acepta paquetes de clientes (a menudo muchos megabytes) y los envía a otros clientes. El servidor nunca almacena explícitamente ninguno de los paquetes entrantes/salientes. Sin embargo, el servidor se ejecuta continuamente en OutOfMemoryException excepciones.Forzar la eliminación explícita de un objeto Java

Agregué System.gc() en el componente de paso de mensajes del servidor, con la esperanza de que se liberara la memoria. Además, establezco el tamaño de almacenamiento dinámico de la JVM en un gigabyte. Todavía recibo tantas excepciones.

Así que mi pregunta es esta: ¿cómo puedo asegurarme de que los mensajes megabyte no se pongan en cola indefinidamente (a pesar de no ser necesarios)? ¿Hay alguna forma de que llame a "eliminar" en estos objetos para garantizar que no están utilizando mi espacio de almacenamiento dinámico?

 try 
     { 
      while (true) 
      { 
       int r = generator.nextInt(100);//generate a random number between 0 and 100 
       Object o =readFromServer.readObject(); 
       sum++; 
       // if the random number is larger than the drop rate, send the object to client, else 
       //it will be dropped 
       if (r > dropRate) 
       { 
        writeToClient.writeObject(o); 
        writeToClient.flush(); 
        numOfSend++; 
        System.out.printf("No. %d send\n",sum); 
       }//if 

      }//while 
     }//try 
+0

Para responder algunas de las respuestas aquí, mi código no almacena ninguna referencia. El servidor es un proxy Socks que pasa objetos. Tengo un ciclo while que lee un objeto de un flujo entrante y lo escribe en un flujo saliente. Eso es. Examinando los perfiladores de memoria ahora. –

+2

leyó el capítulo de "Java efectivo" muy rápidamente;) – Bozho

+0

¿cierra esas corrientes? Dé un código – Bozho

Respuesta

3

Mirando su código: ¿son sus instancias ObjectInput/OutputStream recién creadas cada vez que llega o se envía un paquete, y si es así, se cierran correctamente? Si no, ¿llama al reset() después de cada lectura/escritura? Las clases de flujo de objetos mantienen una referencia a todos los objetos que han visto (para evitar reenviar el mismo objeto cada vez que se lo envía), lo que impide que se recolecten elementos no utilizados. Tuve ese problema exacto hace unos 10 años, la primera vez que tuve que usar un generador de perfiles para diagnosticar una fuga de memoria ...

+0

Hola ... si puedo preguntar, podría ser aplicable a mi pregunta anterior: http://stackoverflow.com/ questions/13488893/java-excel-poi-stops-after-multiple-execution-by-quartz – ides

+0

Mi programa se detiene cada vez que está a punto de escribir el archivo de Excel ... podría ser el outputtrain el motivo por el que se está deteniendo. ¡Gracias! – ides

+0

@ides: no, no puede ser el mismo problema: su código crea una nueva secuencia de salida y la cierra después. –

3

No se puede llamar a la recolección de basura explícita. Pero este no es el problema aquí. Quizás esté almacenando referencias a estos mensajes. Rastree dónde se manejan y asegúrese de que ningún objeto tenga referencia a ellos después de que se hayan utilizado.

Para tener una mejor idea de lo que son las mejores prácticas, leer Effective Java, chapter 2 - se trata de "Creación y destrucción de objetos"

+1

Puede llamar a una recolección de basura explícita, aunque no se garantiza que se ejecute. –

+0

, entonces no es explícito. :) – Bozho

+1

Heh, creo que quiso decir "Puedes llamar explícitamente a GC". La llamada es explícita, el GC no es ...;) – TMN

11

Cuando JVM es en un borde de OutOfMemoryError, que se ejecute el GC.

Llamar por teléfono al System.gc() de antemano no solucionará el problema. El problema es arreglarlo en otro lado. Básicamente existen dos formas:

  1. Escriba el código de memoria eficiente y/o corrija las fugas de memoria en su código.
  2. Dele a JVM más memoria.

El uso de un Java Profiler puede proporcionar mucha información sobre el uso de la memoria y posibles fugas de memoria.

actualización: según su edición con más información sobre el código que está causando este problema, echar un vistazo a Geoff Reedy's answer in this topic que sugiere utilizar ObjectInputStream#readUnshared() y ObjectOutputStream#writeUnshared() lugar. Los Javadocs (vinculados) también lo explican bastante bien.

+3

Personalmente, los reordenaba. :) –

+1

@mmyers, 2 es ciertamente más fácil, pero si tienes una fuga simplemente estás retrasando el problema ligeramente;) – Paolo

+0

@mmyers: hecho :) – BalusC

2

No puede forzar explícitamente la eliminación, pero PUEDE asegurarse de que las referencias a los mensajes no se conserven manteniendo solo una referencia directa en la memoria, y luego utilizando objetos de referencia para mantener referencias recolectables de basura.

¿Qué hay de usar una cola (pequeña, de tamaño limitado) para los mensajes a procesar, y luego una cola secundaria de SoftReference que se alimenta a la primera cola? De esta manera usted garantiza que el procesamiento continuará PERO también que no saldrá de errores de memoria si los mensajes son demasiado grandes (la cola de referencia se abandonará en ese caso).

1

Si obtiene excepciones de OutOfMemory, algo claramente sigue manteniendo una referencia a estos objetos. Puede utilizar una herramienta como jhat para averiguar dónde se quedan estas referencias.

1

Necesita averiguar si está reteniendo objetos por más tiempo de lo necesario.El primer paso sería obtener un generador de perfiles en el caso y observar el montón y ver por qué no se están recopilando los objetos.

Aunque haya entregado el JVM de 1GB, es posible que su generación joven sea demasiado pequeña si se crean muchos objetos muy rápidamente forzándolos a generaciones anteriores donde no se eliminarán tan rápido.

Algo de información útil sobre la sintonización GC: http://java.sun.com/docs/hotspot/gc5.0/gc_tuning_5.html

4

System.gc() es sólo una recomendación a la Máquina Virtual de Java. Usted lo llama y la JVM puede o no ejecutar la recolección de basura.

La excepción OutOfMemoryException puede deberse a dos cosas. O bien conserva referencias (no deseadas) a sus objetos o acepta muchos paquetes.

El primer caso puede ser analizado por un generador de perfiles, donde intenta averiguar cuántas referencias siguen en vivo. Una buena indicación para un puerro de memoria es el creciente consumo de memoria de su servidor. Si cada solicitud adicional hace que su proceso Java crezca un poco, es probable que mantenga referencias en alguna parte (jconsole podría ser un buen comienzo)

Si acepta más datos de los que puede manejar, deberá bloquear las solicitudes adicionales hasta que otros estén completos.

1

el servidor acepta paquetes de clientes (a menudo muchos megabytes) y los reenvía a otros clientes.

Su código probablemente recibe los "paquetes" por completo antes de reenviarlos. Esto significa que necesita suficiente memoria para almacenar todos los paquetes por completo hasta que se hayan reenviado por completo, y cuando esos paquetes son "muchos megabytes de gran tamaño", significa que realmente necesita mucha memoria. también da como resultado una latencia innecesaria.

Es posible que tenga una pérdida de memoria también, pero si lo anterior es cierto, este diseño de "almacenar y reenviar" es su mayor problema. Probablemente pueda reducir el uso de memoria en un 95% si rediseña la aplicación para que no reciba paquetes por completo y en su lugar la envíe directamente a los clientes, es decir, lea solo una pequeña parte del paquete a la vez y transmítalo a los clientes de inmediato. No es difícil hacer esto de una manera que se ve exactamente igual para los clientes que cuando almacena y reenvía.

+0

Normalmente me habría ido con este diseño, pero es importante que mi aplicación deje caer cierto porcentaje de los "paquetes". Se supone que la aplicación de servidor proxy socks abstrae el entorno de un entorno de red hostil. Estoy ejecutando esquemas de codificación de borrado a través de este proxy (cada "paquete" es un bloque de códigos LT). –

+0

@ George George: no entiendo por qué * tiene * que soltar paquetes a menos que esté escribiendo pruebas, pero incluso entonces, ¿no podría decidir si deja o no un paquete en particular cuando llega, y en ese caso, ¿devuelve un error al cliente de origen o simplemente agota el flujo de entrada sin almacenar los datos? –

0

Activación manual de System.gc no es una buena respuesta, ya que otros han publicado aquí. No se garantiza que se ejecute, y desencadena un gc completo, que es probable que cuelgue su servidor por un tiempo prolongado mientras se ejecuta (> 1 segundo si le está dando a su servidor un GB de ram, he visto varios minutos largas pausas en sistemas más grandes). Puedes sintonizar tu gc, lo que sin duda te ayudará, pero no solucionará el problema por completo.

Si está leyendo objetos de una secuencia y luego los escribe en otra, hay un punto en el que mantiene todo el objeto en la memoria. Si estos objetos son, como dices, grandes, entonces ese podría ser tu problema. Intente volver a escribir su IO para que pueda leer los bytes de 1 secuencia y escribirlos en otra sin tener que mantener explícitamente el objeto completo (aunque no puedo ver cómo funcionaría esto con la serialización/deserialización de objetos si necesita verificar/validar los objetos)

0

para agregar a todas las respuestas anteriores: System.gc() no es un comando para que la JVM inicie la recolección de basura ... es una dirección mansa y no garantiza que nada suceda. La especificación de JVM deja que los proveedores llamen para saber qué se necesita hacer en las llamadas gc. ¡Los vendedores incluso pueden elegir no hacer nada en absoluto!

+0

FWIW, cada vez que he llamado 'System.gc()' en las JVM de Sun y de IBM, ya lo ha hecho. No es una llamada síncrona, supongo que solo pone el hilo del GC en la cola de ejecución y regresa. El hilo GC se ejecuta y se suspende de nuevo. Sí, técnicamente no tiene que ser así, y ciertamente no dependería de ello, pero definitivamente vale la pena intentarlo como un paso de depuración. Ah, y los finalizadores tienden a ser ejecutados también. Las mismas advertencias, pero las mismas observaciones también. – TMN

+0

Todo lo que digo es que no es confiable. sun e IBM pueden haberlo implementado. es posible que algún proveedor x no tenga ... – Aadith

19

Las secuencias de objetos contienen referencias a cada objeto escrito/leído de ellas. Esto se debe a que el protocolo de serialización permite referencias posteriores a objetos que aparecieron antes en la transmisión. Es posible que aún pueda utilizar este diseño, pero use writeUnshared/readUnshared en lugar de writeObject/readObject. Creo, pero no estoy seguro, que esto evitará que las transmisiones mantengan una referencia al objeto.

Como dice Cowan, el método reset() también está en juego aquí. Lo más seguro que hacer es, probablemente, utilizar writeUnshared seguido inmediatamente por reset() al escribir a su ObjectOutputStream s

+1

No se puede votar lo suficiente, porque esta es casi con certeza la respuesta. Un par de cosas adicionales: sí, writeUnshared hará exactamente lo que dices. Curiosamente, no está garantizado por el Javadoc, pero la implementación de Sun ciertamente funciona como usted describe. En segundo lugar, la otra opción (especialmente si quiere una retro-referencia limitada, es decir, escribe un 'clúster' de objetos que se refieren el uno al otro pero luego el siguiente 'clúster' está separado) es usar ObjectOutputStream.reset() entre 'lotes' , que explícitamente "ignora [s] el estado de cualquier objeto ya escrito en la secuencia". – Cowan

+0

Aquí hay uno más +1 para finalmente terminar más alto que mi respuesta :) – BalusC

+0

@BalusC - Gracias;) –

0

Usted menciona explícitamente que necesita el conjunto de paquetes recibidos antes de poder enviarlo? Bueno, eso no significa que necesites almacenarlo todo en la memoria, ¿verdad? ¿Es un cambio arquitectónico factible guardar los paquetes recibidos en un almacén externo (tal vez un disco RAM o DB si incluso un SSD es demasiado lento) y luego canalizarlos directamente al destinatario sin tener que cargarlos completamente en la memoria?

0

Si su servidor se ejecuta durante al menos unos minutos antes de que se muera, es posible que desee intentar ejecutarlo en Visual VM. Al menos, puede obtener una mejor idea de qué tan rápido está creciendo el montón, y qué tipo de objetos hay en él.

Cuestiones relacionadas