java - tirar - ¿Por qué los objetos de corta y larga vida son una diferencia en la recolección de basura?
tesis de residuos solidos municipales (8)
(Consulte las explicaciones anteriores para GC más general .. esto responde POR QUÉ es más barato para GC que antiguo).
La razón por la que eden puede borrarse más rápido es simple: el algoritmo es proporcional al número de objetos que sobrevivirán a GC en el espacio eden, no proporcional al número de objetos vivos en todo el montón. es decir: si tiene una tasa promedio de muerte de objetos del 99% en eden (es decir, el 99% de los objetos no sobreviven al GC, lo que no es anormal), solo necesita mirar y copiar ese 1%. Para GC "viejo", todos los objetos vivos en el montón completo deben marcarse / barrerse. Eso es significativamente más caro.
A menudo he leído que en Sun JVM, los objetos de corta duración ("objetos relativamente nuevos") se pueden recolectar de manera más eficiente que los objetos de larga duración ("objetos relativamente antiguos")
- ¿Por qué es así?
- ¿Es eso específico de Sun JVM o es el resultado de un principio general de recolección de basura?
Ahí está el fenómeno de que "la mayoría de los objetos mueren jóvenes". Muchos objetos se crean dentro de un método y nunca se almacenan en un campo. Por lo tanto, tan pronto como el método salga, estos objetos "mueren" y, por lo tanto, son candidatos para la recolección en el siguiente ciclo de recolección.
Aquí hay un ejemplo:
public String concatenate(int[] arr) {
StringBuilder sb = new StringBuilder();
for(int i = 0; i < arr.length; ++i)
sb.append(i > 0 ? "," : "").append(arr[i]);
return sb.toString();
}
El objeto sb
se convertirá en basura tan pronto como el método regrese.
Al dividir el espacio del objeto en dos (o más) áreas basadas en la edad, el GC puede ser más eficiente: en lugar de escanear con frecuencia todo el montón, el GC escanea con frecuencia solo el vivero (el área de objetos jóvenes), lo cual, obviamente, requiere mucho Menos tiempo que un montón de escaneo. El área de objetos más antiguos se escanea con menos frecuencia.
Esta es la recolección de basura generacional . Se usa bastante ampliamente en estos días. Ver más aquí: (wiki) .
Esencialmente, el GC asume que es más probable que los nuevos objetos se vuelvan inalcanzables que los antiguos.
Esto se basa en la observación de que la esperanza de vida de un objeto aumenta a medida que envejece. Por lo tanto, tiene sentido mover los objetos a un grupo recolectado con menos frecuencia una vez que alcanzan cierta edad.
Esta no es una propiedad fundamental de la forma en que los programas usan la memoria. Podría escribir un programa patológico que mantuviera todos los objetos durante un tiempo prolongado (y el mismo tiempo para todos los objetos), pero esto no suele ocurrir por accidente.
La JVM (normalmente) utiliza un recolector de basura generacional. Este tipo de colector separa la memoria del montón en varias agrupaciones, de acuerdo con la edad de los objetos allí. El razonamiento aquí se basa en la observación de que la mayoría de los objetos son de corta duración, de modo que si realiza una recolección de basura en un área de la memoria con objetos "jóvenes", puede recuperar relativamente más memoria que si realiza la recolección de basura en "más antiguos " objetos.
En la JVM Hotspot, los nuevos objetos se asignan en el área llamada Eden. Cuando esta área se llena, la JVM barrerá el área de Eden (lo cual no toma mucho tiempo, porque no es tan grande). Los objetos que aún están vivos se mueven al área de Sobrevivientes, y el resto se desecha, liberando a Eden para la próxima generación. Cuando la colección Eden no es suficiente, el recolector de basura pasa a las generaciones anteriores (lo que requiere más trabajo).
La mayoría de las aplicaciones Java crean objetos Java y luego los descartan con bastante rapidez, por ejemplo. creas algunos objetos en un método y, una vez que sales del método, todos los objetos mueren. La mayoría de las aplicaciones se comportan de esta manera y la mayoría de las personas tienden a codificar sus aplicaciones de esta manera. El montón de Java se divide aproximadamente en 3 partes, generación permanente, antigua (larga vida) y generación joven (corta vida). La generación joven se divide en S1, S2 y eden. Estos son sólo montones.
La mayoría de los objetos son creados en el gen joven. La idea aquí es que, dado que la tasa de mortalidad de los objetos es alta, los creamos rápidamente, los usamos y luego los descartamos. La velocidad es esencial. A medida que creas objetos, el gen joven se llena hasta que se produce un CG menor. En un GC menor, todos los objetos que están vivos se copian de eden y dicen S2 a S1. Luego, el ''puntero'' descansa sobre eden y S2.
Cada copia envejece el objeto. Por defecto, si un objeto sobrevive 32 copias a saber. 32 GC menor, luego el GC calcula que va a durar mucho más. Entonces, lo que hace es mantenerlo, moviéndolo a la vieja generación. La vieja generación es solo un gran espacio. Cuando el gen antiguo se llena, un GC completo, o GC mayor, ocurre en el gen antiguo. Como no hay otro espacio para copiar, el GC tiene que compactarse. Esto es mucho más lento que un GC menor, por eso evitamos hacer eso con más frecuencia.
Puede ajustar el parámetro de tenencia con
java -XX:MaxTenuringThreshold=16
Si sabes que tienes muchos objetos de larga vida. Puede imprimir los distintos grupos de edad de su aplicación con
java -XX:-PrintTenuringDistribution
Los objetos jóvenes se administran de manera más eficiente (no solo se recopilan, sino que los accesos a los objetos jóvenes también son más rápidos) porque se asignan en un área especial (la "generación joven"). Esa área especial es más eficiente porque se recopila "de una vez" (con todos los subprocesos detenidos) y ni el recopilador ni el código aplicativo tienen que lidiar con el acceso concurrente del otro.
La compensación aquí es que el "mundo" se detiene cuando se recolecta el "área eficiente". Esto puede inducir una pausa notable. La JVM mantiene los tiempos de pausa bajos al mantener el área eficiente lo suficientemente pequeña. En otras palabras, si hay un área administrada de manera eficiente, entonces esa área debe ser pequeña.
Una heurística muy común, aplicable a muchos programas y lenguajes de programación, es que muchos objetos tienen una vida muy corta y la mayoría de los accesos de escritura se producen en objetos jóvenes (aquellos que se crearon recientemente). Es posible escribir código de aplicación que no funcione de esa manera, pero estas heurísticas serán "en su mayoría verdaderas" en "la mayoría de las aplicaciones". Por lo tanto, tiene sentido almacenar objetos jóvenes en el área de administración eficiente. Que es lo que hace la JVM GC y por eso esa área eficiente se llama la "generación joven".
Tenga en cuenta que hay sistemas donde toda la memoria se maneja "eficientemente". Cuando el GC debe ejecutarse, la aplicación se "congela" durante unos segundos. Esto es inofensivo para los cálculos a largo plazo, pero perjudicial para la interactividad, por lo que la mayoría de los entornos de programación modernos habilitados para GC utilizan GC generacional con una generación joven de tamaño limitado.
Todos los GC se comportan de esa manera. La idea básica es que intenta reducir la cantidad de objetos que necesita revisar cada vez que ejecuta el GC porque esta es una operación bastante costosa. Entonces, si tienes millones de objetos pero solo necesitas revisar algunos, eso es mucho mejor que tener que revisarlos todos. Además, una característica de GC juega en tus manos: los objetos temporales (que ya no pueden ser alcanzados por nadie), no tienen costo durante la ejecución de GC (bueno, ignoremos el método finalize()
por ahora). Sólo los objetos que sobreviven cuestan tiempo de CPU. A continuación, se observa que muchos objetos son de corta duración.
Por lo tanto, los objetos se crean en un espacio pequeño (llamado "Eden" o "gen joven"). Después de un tiempo, todos los objetos que se pueden alcanzar se copian (= costosos) fuera de este espacio y el espacio se declara vacío (por lo que Java se olvida efectivamente de todos los objetos inalcanzables, por lo que no tienen un costo ya que no lo hacen). Hay que copiarlo). Con el tiempo, los objetos de larga vida se mueven a espacios "más antiguos" y los espacios más antiguos se barren con menos frecuencia para reducir la sobrecarga del GC (por ejemplo, cada N se ejecuta, el GC ejecutará un espacio antiguo en lugar del espacio eden).
Solo para comparar: si asigna un objeto en C / C ++, debe llamar a free()
más el destructor para cada uno de ellos. Esta es una de las razones por las que GC es más rápido que la administración de memoria manual tradicional.
Por supuesto, este es un aspecto bastante simplificado. Hoy en día, trabajar en GC se encuentra en el nivel de diseño del compilador (es decir, hecho por muy pocas personas). Los GC hacen todo tipo de trucos para hacer que todo el proceso sea eficiente e imperceptible. Vea el artículo de Wikipedia para algunos consejos.