2012-01-11 13 views
12

Estaba leyendo "CLR mediante C#" y parece que en este ejemplo, el objeto que se asignó inicialmente a 'obj' será elegible para Garbage Collection después de que se ejecute la línea 1, no después línea 2.Vida útil de los objetos en Java vs .Net

void Foo() 
{ 
    Object obj = new Object(); 
    obj = null; 
} 

Eso es porque la vida útil variable local no se define por el alcance en el que se define, sino por la última vez que se leyó.

Entonces mi pregunta es: ¿qué hay de Java? He escrito este programa para verificar tal comportamiento, y parece que el objeto se mantiene vivo. No creo que sea posible para la JVM limitar la vida útil de la variable mientras se interpreta bytecode, así que traté de ejecutar el programa con 'java -Xcomp' para forzar la compilación de métodos, pero 'finalizar' no se llama de todos modos. Parece que eso no es cierto para Java, pero espero que pueda obtener una respuesta más precisa aquí. Además, ¿qué pasa con la máquina virtual Dalvik de Android?

class TestProgram { 

    public static void main(String[] args) { 
     TestProgram ref = new TestProgram(); 
     System.gc(); 
    } 

    @Override 
    protected void finalize() { 
     System.out.println("finalized"); 
    } 
} 

Agregado: Jeffrey Richter da ejemplo de código en "CLR a través de C#", algo como esto:

public static void Main (string[] args) 
{ 
    var timer = new Timer(TimerCallback, null, 0, 1000); // call every second 
    Console.ReadLine(); 
} 

public static void TimerCallback(Object o) 
{ 
    Console.WriteLine("Callback!"); 
    GC.Collect(); 
} 

TimerCallback llama sólo una vez en MS .Net si los proyectos objetivo es 'Release' (temporizador destruido después de GC.Collect() llamada), y llamado cada segundo si el objetivo es 'Debug' (las variables de la vida útil aumentan porque el programador puede intentar acceder al objeto con el depurador). Pero en la devolución de llamada Mono se llama cada segundo, sin importar cómo lo compile. Parece que la implementación de Mono "Timer" almacena referencias a la instancia en algún lugar del grupo de subprocesos. La implementación de MS no hace esto.

+10

No he podido encontrar esto antes en las especificaciones de Java. Tenga en cuenta que .NET puede ser aún más loco de lo que cabría esperar: es posible que se recopile una instancia * mientras se ejecuta un método de instancia * si el CLR sabe que no se hará referencia a más variables de instancia. –

Respuesta

4

Tenga en cuenta que el hecho de que un objeto puede puede recopilarse, no significa que realmente se recopile un punto determinado, por lo que su método puede dar falsos negativos. Si se llama al método finalize de cualquier objeto, definitivamente puede decir que era inalcanzable, pero si no se llama al método, no puede deducir nada lógicamente. Al igual que con la mayoría de las preguntas relacionadas con GC, el no determinismo del recolector de basura hace que sea difícil encontrar pruebas/garantías sobre qué hará exactamente.

Sobre el tema de la accesibilidad/cobrarlas, el JLS dice (12.6.1):

Un alcanzable objeto es cualquier objeto que se puede acceder en cualquier computación continua potencial de cualquier tema en vivo. Se pueden diseñar transformaciones de optimización de un programa que reduzcan la cantidad de objetos accesibles para que sean inferiores a los que ingenuamente se considerarían alcanzables. Por ejemplo, un compilador o generador de código puede optar por establecer una variable o parámetro que ya no se utilizará para anular para hacer que el almacenamiento de dicho objeto sea potencialmente recuperable antes.

Cuál es más o menos exactamente lo que usted esperaría - pienso que el párrafo antedicho es isomorfo con "un objeto es inalcanzable iff usted definitivamente no lo usará más".

Volviendo a su situación original, ¿puede pensar en alguna ramificación práctica entre el objeto que se considera inalcanzable después de la línea 1 en comparación con la línea 2? Mi reacción inicial es que no hay ninguno, y si de alguna manera lograste encontrar una situación así, probablemente sería una marca de código incorrecto/retorcido que haría que el VM tuviera dificultades en lugar de una debilidad inherente en el lenguaje.

Aunque estoy abierto a contraargumentos.


Edit: Gracias por el interesante ejemplo.

Estoy de acuerdo con su evaluación y veo a dónde va, aunque el problema es probablemente más que el modo de depuración está cambiando sutilmente la semántica de su código.

En el código tal como está escrito, asigne el Timer a una variable local que no se lee posteriormente dentro de su alcance. Incluso el análisis de escape más trivial puede revelar que las variables timer no se usan en ningún otro lugar en el método main, por lo que se pueden elidir. Por lo tanto creo que su primera línea se puede considerar exactamente equivalente a simplemente invocando el constructor directamente:

public static void Main (string[] args) 
{ 
    new Timer(TimerCallback, null, 0, 1000); // call every second 
    ... 

En este último caso, está claro que la recién creada Timer objeto no es accesible inmediatamente después de la construcción (suponiendo que doesn' hacer cualquier cosa furtiva como agregarse a campos estáticos, etc. en su constructor); y para que se recolectara tan pronto como el CG lo consiguiera.

Ahora en el caso de depuración las cosas son sutilmente diferentes, por la razón que ha mencionado que el desarrollador puede desear inspeccionar el estado de las variables locales más adelante en el método. Por lo tanto, el compilador (y el compilador JIT) no pueden optimizarlos; es como si hubiera un acceso de la variable al final del método, lo que impide la recopilación hasta ese punto.

Aun así, no creo que esto realmente cambie la semántica. La naturaleza de GC es que la recopilación rara vez está garantizada (al menos en Java, la única garantía que obtienes es que si se lanzaba un OutOfMemoryError, todo lo que se consideraba inalcanzable se GCed inmediatamente antes). De hecho, suponiendo que tenía suficiente espacio en el montón para contener todos los objetos creados durante la vida útil del motor de ejecución, una implementación GC no operativa es perfectamente válida. Por lo tanto, si bien puede observar cambios de comportamiento en el número de veces que Timer marca, esto está bien ya que no hay garantías sobre lo que verá en función de la forma en que lo está invocando. (Esto es conceptualmente similar a cómo un temporizador que se ejecuta durante una tarea intensiva de CPU marcaría más veces cuando el sistema estaba bajo carga; ninguno de los resultados es incorrecto porque la interfaz no proporciona ese tipo de garantía.)

En este punto te remito a la primera oración en esta respuesta. :)

+0

Gracias por responder. Editaré mi pregunta y agregaré un ejemplo de código allí. – Ivan

+1

He agregado un ejemplo de código a mi publicación, muestra un comportamiento diferente del programa en diferentes implementaciones de.Neto y con diferente objetivo de compilación, causado por esa optimización. – Ivan

+0

Vida útil variable en el entorno subyacente! = A la del lenguaje, tal comportamiento no es intuitivo y en teoría puede romper el código, aunque no creo que eso sea realmente un problema. Solo me pregunto si las especificaciones de Java dicen algo sobre tales casos. Usar GC se ve cada vez más complicado todos los días :) – Ivan

1

Java, en general, tiene el comportamiento de que si el objeto es alcanzable en el alcance (hay una referencia que no es basura), entonces el objeto no es basura. Esto es recursivo, por lo que si a es una referencia a un objeto que tiene una referencia a b, el objeto b se refiere a no es basura.

Dentro del ámbito donde todavía se puede llegar a el objeto referenciado por ref, (puede añadir una línea de la System.out.println(ref.toString())), ref no es basura.

Sin embargo, de acuerdo con this old source from Sun's site, la mayoría depende de la implementación específica de la JVM.

Cuestiones relacionadas