2011-08-12 14 views
9

He estado trabajando en una pérdida de cargador de clase en nuestra aplicación y finalmente llegué al punto donde todas las referencias a la CL se habían ido. En mi herramienta de perfilado de memoria (usando YourKit y jmap/jhat), puedo forzar un GC que a veces eliminará inmediatamente mi cargador de clases, pero luego otras veces (depende del uso de la aplicación pero eso es lo más específico que puedo) cuando fuerzo el GC, la instancia de CL no desaparece. Capturo una instantánea de memoria y miro los resultados y dice que este objeto existe, pero es inalcanzable.¿Por qué la JVM tarda tanto en GC mi objeto inalcanzable?

Aquí está la parte loca ... y solo descubrí esto por accidente.

Puedo forzar los GC completos todo lo que quiero, pero la instancia simplemente no desaparece. SIN EMBARGO, después de 10-20 minutos, si hago otro GC completo, se obtiene .

(Durante este período de tiempo la aplicación es en su mayoría inactivos (no totalmente). Mi café "extendida" fue el accidente que llevó a este descubrimiento.)

Así que mi preocupación por una fuga que aquí se ha ido (con suerte), pero la pregunta ahora es más tratar de explicar este comportamiento.

¿Alguien sabe qué podría hacer que la JVM del sol decida no recoger un cargador de clase inalcanzable durante 20 minutos?

Sun JVM detalles de la versión:

java version "1.6.0_23" 
Java(TM) SE Runtime Environment (build 1.6.0_23-b05) 
Java HotSpot(TM) 64-Bit Server VM (build 19.0-b09, mixed mode) 
+0

Esto podría ser útil: http://java.sun.com/developer/technicalArticles/javase/finalization/ – Jeremy

+0

Otro dato más probablemente sea relevante: aquí estoy usando el colector paralelo. – Scott

+0

Mi experiencia es bastante similar a la tuya y siempre podría explicarlo de la misma manera que un GC apropiado es algo complejo de lograr. Una interesante "discusión" al respecto va aquí: http://stackoverflow.com/questions/176745/circular-references-in-java. Hay algunos casos especiales manejados por un recolector de basura, uno de ellos son los cargadores de clase, y otros son referencias débiles. Si lo comprueba leyendo un código fuente de GC real o una respuesta _proven_, publíquelo aquí. Otro artículo sobre GC en Java5 en detalle: http://www.oracle.com/technetwork/java/gc-tuning-5-138395.html –

Respuesta

0

probable es que la JVM utiliza generational recolección de basura, pero con el tiempo lo haría un total de marcaje y barrido

+0

A menos que no entienda el significado de los términos, viendo el PS MarkSweep major el incremento del contador de colecciones (y la caída resultante en el uso del montón de generación de tiempo) me parece indicar que el barrido completo sí ocurre cuando lo solicité. – Scott

0

Sólo una conjetura: el cargador de clases (y las clases cargó) no se encuentran en el montón habitual, sino en la generación permanente ("PermGen"). Esta parte de la memoria se pasará con mucha menos frecuencia que cualquier otra cosa, ya que en condiciones normales, los cargadores de clase no salen del alcance.

(Puede haber alguna opción de configuración del GC para influir en esta frecuencia.) Supongo que al llenar el PermGen se activará una colección allí, también, pero normalmente no desea hacer esto.

¿Por qué necesita recopilar su cargador de clases, de hecho?

+0

A riesgo de simplificar en exceso nuestra necesidad de GC en la CL, hay "módulos enchufables" que pueden entrar y salen en la aplicación que aportan código, configuración y otros recursos. Creamos y usamos un cargador de clases especial para estos módulos para cumplir con nuestros requisitos, pero cuando un módulo funciona, queremos reclamar la memoria que consumió, incluido permgen. – Scott

+0

Supongo que desaparecerá cada vez que un nuevo módulo necesite este espacio ... aunque no tuve ninguna experiencia con un sistema de módulos tan dinámico. –

5

Creo que Jeremy Heiler está en el camino correcto ya que es un problema de finalización. Básicamente, incluso si el GC sabe que un objeto es inalcanzable, aún puede pasar un tiempo indefinido antes de que el objeto realmente se recopile, ya que puede ponerse en cola para su finalización. El hilo del finalizador tendrá que pasar por la finalización del objeto (lo que podría demorar un tiempo, dependiendo de cuántos otros objetos se tengan que finalizar y de cómo se vean sus finalizadores) y luego, una vez finalizado el objeto, necesitarás otro GC para volver a descubrir que el objeto es inalcanzable antes de que realmente se recopile.

Además, en el caso de un cargador de clases en una JVM de Sun, es probable que se asigne directamente a PermGen. Por lo tanto, no necesitará uno, sino dos barridos completos de PermGen (uno para colocar el objeto en la cola de finalización y luego otro para recopilarlo). Como los barridos PermGen son caros, creo que Sun JVM no los hace en absoluto con la configuración predeterminada, e incluso con varias afinaciones de GC, todavía es bastante reacio a barrer PermGen (especialmente si no hay mucha otra presión de memoria).)

Si en vez de forzar (um, quiero decir, el fomento de ) usando un GC System.gc(), lo que si haces algo como:

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

Por supuesto, incluso esto no se garantiza que funcione, pero al menos hace el trabajo mínimo necesario para limpiar un gran objeto requerido de finalización.

+0

Podría estar bien ... pero no puedo probarlo dado el comportamiento aparentemente no determinista que estoy viendo. Al final, mi CL se recoge y puedo ver incluso el descenso del uso de permgen, lo cual es bueno. Simplemente no me gustan los comportamientos no deterministas, especialmente cuando podría volver y morderme más tarde de un problema de producción. :) – Scott

+0

@Scott: Desafortunadamente, la verdadera recolección de basura siempre es no determinista. No hay nada que puedas hacer al respecto; si realmente necesita un comportamiento determinista, debe consultar [Java en tiempo real] (http://en.wikipedia.org/wiki/Real-time_specification_for_Java). –

0

Una posible razón es que el cargador de clases estaba retenido por referencias suaves. Intente ejecutar con

-XX:SoftRefLRUPolicyMSPerMB=1 

y vea si eso hace la diferencia. Eso es lo que sucedió en my case.

Cuestiones relacionadas