Escuché a una asociada decir:

Los tiempos de recolección de basura de JVM aumentan exponencialmente con el tamaño de JVM. Esto se debe a que el árbol de referencias es una función de la cantidad de objetos a asignar, y se vuelve exponencialmente más difícil atravesar el árbol a medida que aumenta el número de objetos.

Esto sonaba bien.

Escuché a otra asociada decir:

La recolección de basura JVM en la misma máquina es lineal. Dada una JVM de 8GB dividida en dos JVM 4G en la misma máquina (a través de microservicios), tendrá la misma duración de recolección de basura porque el mismo sistema operativo lo ralentiza para la misma cantidad de objetos.

Esto no parecía correcto, ya que los árboles de objetos en las dos JVM más pequeñas deberían ser menos profundos y más fáciles de atravesar.

Mi pregunta es: ¿Los tiempos de recopilación de JVM aumentan exponencialmente con el tamaño de RAM de JVM?

Suposición: Oracle JVM utilizada.

1
hawkeye 14 feb. 2018 a las 07:49

2 respuestas

La mejor respuesta

Si bien la explicación de Holger es correcta, me gustaría darle un aspecto ligeramente diferente.El tiempo que tarda un GC es directamente proporcional al número de objetos en vivo en el set en vivo. Esto se demuestra fácilmente. Suponga que tenemos dos aplicaciones con montones del mismo tamaño. En el primer montón asignamos 10 objetos de 100 MB cada uno y en el segundo 10 millones de 100 bytes cada uno. En el siguiente GC, la mitad de los objetos de cada aplicación son inalcanzables (muertos) y se pueden recolectar.

Es evidente que se necesitará más tiempo para trazar el gráfico con la mayoría de los objetos.

(Como comentario al margen, recuerdo haber leído una medida de 'poco profundo y ancho' versus 'profundo y estrecho' y que no había una diferencia perceptible, pero no recuerdo dónde. @Holger: si tiene una fuente, me encantaría léelo)

Tenga en cuenta que seguir las prácticas de codificación java establecidas garantizará de hecho que el conjunto en vivo sea pequeño. La JVM espera que codifique de esa manera y hace todo lo posible para ayudar a mantener el conjunto en vivo pequeño, análisis de escape es solo un truco en la manga de los puntos calientes.

Entonces, en resumen: NO

2
Erik 14 feb. 2018 a las 13:06

No existe una dependencia tan simple.

En primer lugar, considerar la "recolección de basura" como una función sobre las referencias de objetos obviamente solo se refiere a la fase de marcado , ignorando los costos de asignación, desasignación o copia / movimiento de objetos. Los costes de marcado dependen del número de referencias vivas que deban atravesarse, ni los objetos muertos ni la memoria no utilizada tienen ningún impacto en él. Por lo tanto, simplemente dar más RAM a la misma aplicación no cambia necesariamente el rendimiento de la recolección de basura en absoluto.

Existe una tendencia a usar cualquier cantidad de RAM que le dé a la JVM, por lo que proporcionar más RAM puede hacer que los ciclos de recolección de basura sean menos frecuentes, pero tal vez necesite más tiempo para marcar todos los objetos activos. Pero dado que tener más tiempo entre recolecciones de basura aumenta las posibilidades de que los objetos no se utilicen, los costos de marcado generalmente no escalan en el mismo factor que el tiempo entre recolecciones.

Es fácil demostrar que en realidad es al revés en la práctica. Simplemente tome una aplicación Java arbitraria y reduzca la memoria disponible hasta el punto en que apenas se ejecute sin encontrar un OutOfMemoryError. Verá cómo proporcionar menos RAM lo hace más lento, dramáticamente más lento cuanto más se acerca a ese punto. Por otro lado, en realidad no es necesario demostrar que proporcionar a una aplicación tanta RAM que nunca necesite una recolección de basura durante su vida útil tiene los costos más pequeños.

Cuando miramos solo la fase de marcado, sin considerar la frecuencia con la que sucede, y solo consideramos cómo escala con el número de referencias en vivo, todavía no hay ninguna razón por la que deba ser exponencial. Las referencias a objetos pueden formar un gráfico arbitrario que rara vez es un árbol. Además, el recolector de basura no necesita recorrer cada referencia de objeto. Solo necesita atravesar referencias a objetos que no ha encontrado antes (adivina por qué se llama "marcar"), lo que implica que el número de referencias que necesita atravesar es idéntico al número de objetos activos. Puede haber algunos costos para descubrir que no es necesario atravesar una referencia, pero esto sigue siendo una sobrecarga lineal .

Las JVM como HotSpot (ya no es una propiedad de Sun) usan la recolección de basura generacional y el marcado de tarjetas, solo para atravesar referencias de objetos nuevos y objetos antiguos cuya sección de memoria (tarjeta) ha cambiado, en lugar de todos los objetos activos. Dado que tanto el cambio de objetos antiguos como la creación de nuevos objetos requieren tiempo de CPU, no se escala directamente con la RAM disponible.

1
Holger 14 feb. 2018 a las 11:15