2011-12-29 25 views
13

como ECMAScriptv5, cada vez que el control entra en un código, el enginge crea una LexicalEnvironment (LE) y un VariableEnvironment (VE), por código de función, estos 2 objetos son exactamente la misma referencia que es el resultado de llamar NewDeclarativeEnvironment (ECMAScript v5 10.4.3), y todas las variables declaradas en código de función se almacenan en el registro ambiente componentof VariableEnvironment (ECMAScript v5 10.5), y esto es el concepto básico para el cierre.Sobre el cierre, LexicalEnvironment y GC

Lo que me confunde es cómo limpieza de la memoria obras con este enfoque de cierre, supongamos que tengo un código como:

function f1() { 
    var o = LargeObject.fromSize('10MB'); 
    return function() { 
     // here never uses o 
     return 'Hello world'; 
    } 
} 
var f2 = f1(); 

después de la línea var f2 = f1(), nuestro gráfico de objetos sería:

global -> f2 -> f2's VariableEnvironment -> f1's VariableEnvironment -> o 

por lo que, según mi conocimiento, si el motor de JavaScript usa un método de recuento de referencias para recolección de basura, el objeto o tiene ase 1 refenrence y nunca se GCed. Aparentemente, esto daría lugar a una pérdida de memoria ya que o nunca se usaría, pero siempre se almacena en la memoria.

Alguien puede dijo que el motor sabe que de f2 VariableEnvironment no utiliza VariableEnvironment de la F1, por lo VariableEnvironment los enteros de la F1 sería GCed, por lo que no es otro fragmento de código que puede conducir a la situación más compleja:

function f1() { 
    var o1 = LargeObject.fromSize('10MB'); 
    var o2 = LargeObject.fromSize('10MB'); 
    return function() { 
     alert(o1); 
    } 
} 
var f2 = f1(); 

en este caso, f2 utiliza el objeto o1 que almacena en VariableEnvironment de la F1, por lo VariableEnvironme de f2 nt debe mantener una referencia a VariableEnvironment, lo que significa que o2 tampoco se puede convertir en GC, lo que resulta en un desperdicio de memoria.

así que preguntaría, cómo el motor javascript moderno (JScript.dll/V8/SpiderMonkey ...) maneja tal situación, ¿existe una norma estándar o está basada en la implementación, y cuál es el paso exacto del motor javascript? tal objeto gráfico al ejecutar la recolección de basura.

Gracias.

+0

posible duplicado de [recolección de basura con node.js] (http://stackoverflow.com/questions/5326300/garbage-collection-with-node-js) – Bergi

Respuesta

7

tl; dr respuesta:"Only variables referenced from inner fns are heap allocated in V8. If you use eval then all vars assumed referenced.". En su segundo ejemplo, o2 se puede asignar en la pila y se descarta después de que f1 sale.


No creo que lo puedan soportar.Al menos sabemos que algunos motores no puede, ya que se sabe que es la causa de muchas pérdidas de memoria, como por ejemplo:

function outer(node) { 
    node.onclick = function inner() { 
     // some code not referencing "node" 
    }; 
} 

donde inner se cierra sobre node, formando una referencia circular inner -> outer's VariableContext -> node -> inner, que nunca será liberado por ejemplo, IE6, incluso si el nodo DOM se elimina del documento. Algunos navegadores manejan esto bien: las referencias circulares no son un problema, es la implementación de GC en IE6 el problema. Pero ahora estoy divagando del tema.

Una forma común de romper la referencia circular es anular todas las variables innecesarias al final de outer. Es decir, establezca node = null. La pregunta es si los motores javascript modernos pueden hacer esto por usted, ¿pueden de alguna manera inferir que una variable no se usa dentro de inner?

Creo que la respuesta es no, pero puedo demostrar que estoy equivocado. La razón es que el siguiente código se ejecuta muy bien:

function get_inner_function() { 
    var x = "very big object"; 
    var y = "another big object"; 
    return function inner(varName) { 
     alert(eval(varName)); 
    }; 
} 

func = get_inner_function(); 

func("x"); 
func("y"); 

Vea usted mismo usando this jsfiddle example. No hay referencias a x o y dentro de inner, pero todavía se puede acceder utilizando eval. (Sorprendentemente, si alias eval a otra cosa, digamos myeval, y llama al myeval, NO obtienes un nuevo contexto de ejecución, esto es incluso en la especificación, ver las secciones 10.4.2 y 15.1.2.1.1 en ECMA-262.)


Editar: de acuerdo con su comentario, parece que algunos motores modernos realmente hacen algunos trucos inteligentes, así que traté de profundizar un poco más. Me encontré con este forum thread discutiendo el tema, y ​​en particular, un enlace al a tweet about how variables are allocated in V8. También se refiere específicamente al problema eval. Parece que tiene que analizar el código en todas las funciones internas. y ver a qué variables se hace referencia, o si se usa eval, y luego determinar si cada variable debe asignarse en el montón o en la pila. Con buena pinta. Aquí está another blog que contiene muchos detalles sobre la implementación de ECMAScript.

Esto tiene la consecuencia de que incluso si una función interna nunca "escapa" la llamada, aún puede obligar a las variables a asignarse en el montón. Por ejemplo:

function init(node) { 

    var someLargeVariable = "..."; 

    function drawSomeWidget(x, y) { 
     library.draw(x, y, someLargeVariable); 
    } 

    drawSomeWidget(1, 1); 
    drawSomeWidget(101, 1); 

    return function() { 
     alert("hi!"); 
    }; 
} 

Ahora, como init ha terminado su llamada, someLargeVariable ya no está referenciado y ser elegibles para su eliminación, pero sospecho que no lo es, a menos que la función interna drawSomeWidget ha sido optimizado de distancia (en línea?) Si es así, esto probablemente podría ocurrir con bastante frecuencia al usar funciones autoejecutables para imitar clases con métodos privados/públicos.


Responder a Raynos comenta abajo. Probé el escenario anterior (ligeramente modificada) en el depurador, y los resultados son como Predigo, al menos en Chrome:

Screenshot of Chrome debugger cuando se está ejecutando la función interna, someLargeVariable todavía es en su alcance.

Si comento hacia fuera la referencia a someLargeVariable en el drawSomeWidget método interno, entonces se obtiene un resultado diferente:

Screenshot of Chrome debugger 2 Ahora someLargeVariable no es en su alcance, ya que podría ser asignado en la pila.

+1

Muchas gracias, 'eval' es realmente un monstruo para GC, recientemente un amigo me dijo que algún motor (SpiderMonkey en su caso) puede manejar un GC avanzado a variables de cierre si ** no hay 'eval' en' interior' ** ya que el motor de JavaScript puede recolectar todos los nombres de variable que se usarán en "inner" simplemente tienen que ver ** Identifier ** s en el código de función, pero cuando 'inner' contiene cualquier llamada a' eval', no realiza ninguna optimización. En mi opinión, tratar 'eval' de manera diferente es realmente una estrategia bien diseñada :) – otakustay

+0

Oh, interesante. He visto en algunas bibliotecas que usan el constructor de funciones 'new Function (code-as-string)' como una alternativa a 'eval', que no sufre el" problema "de heredar el alcance, según mis pruebas. Me pregunto si 'setInterval' y' setTimeout' deben manejarse de manera diferente también? – waxwing

+0

@otakustay: respuesta actualizada. – waxwing

0

si el motor de JavaScript utiliza un método de recuento de referencias

La mayoría de javascript motor del uso de alguna variante de un colector de basura compacting mark and sweep, no un simple recuento de referencias GC, por lo que los ciclos de referencia no causan problemas.

También tienden a hacer algunos trucos para que los ciclos que involucran nodos DOM (que son referencias contadas por el navegador fuera del montón de JavaScript) no introducen ciclos incobrables. The XPCOM cycle collector hace esto para Firefox.

El recogedor de ciclos pasa la mayor parte de su tiempo acumulando (y olvidando) punteros a objetos XPCOM que podrían estar involucrados en ciclos de basura. Esta es la etapa inactiva de la operación del recopilador, en la que las variantes especiales nsAutoRefCnt se registran y anulan rápidamente con el recopilador, ya que pasan por un evento de recuento "sospechoso" (de N + 1 a N, para N distinto de cero).

Periódicamente, el recolector se despierta y examina los punteros sospechosos que han estado en su memoria intermedia por un tiempo. Esta es la etapa de escaneo de la operación del colector. En esta etapa, el recopilador le pregunta repetidamente a cada candidato por una clase auxiliar de recolección de ciclo único, y si ese ayudante existe, el recaudador le pide al ayudante que describa a los hijos (del candidato) del candidato. De esta forma, el recolector crea una imagen del subgrafo de propiedad accesible desde objetos sospechosos.

Si el recopilador encuentra un grupo de objetos que todos se refieren entre sí, y establece que los recuentos de referencia de los objetos son contados por punteros internos dentro del grupo, considera la basura cíclica del grupo, que luego intenta liberar. Esta es la etapa de desvinculación de la operación de colectores. En esta etapa, el recolector camina a través de los objetos de basura que ha encontrado, consultando nuevamente con sus objetos auxiliares, pidiéndole a los objetos auxiliares que "desenlacen" cada objeto de sus hijos inmediatos.

Tenga en cuenta que el recopilador también sabe cómo recorrer el montón de JS y puede ubicar los ciclos de propiedad que entran y salen de él.

EcmaScript harmony es probable que incluya ephemerons, así como para proporcionar referencias débilmente retenidas.

Puede que encuentre "The future of XPCOM memory management" interesante.

1

No hay especificaciones estándar de implementación para GC, cada motor tiene su propia implementación. Conozco un pequeño concepto de v8, tiene un recolector de basura muy impresionante (stop-the-world, generational, accurate). Como en el ejemplo anterior 2, el motor v8 tiene el siguiente paso:

  1. cree el objeto VariableEnvironment de f1 llamado f1.
  2. después de crear ese objeto, el V8 crea una clase oculta inicial de f1 llamada H1.
  3. indican que el punto de f1 es a f2 en el nivel raíz.
  4. crear otra clase oculta H2, basada en H1, luego agregue información a H2 que describa que el objeto tiene una propiedad, o1, almacénelo en la compensación 0 en el objeto f1.
  5. actualizaciones f1 punto a H2 indicado f1 debería usar H2 en lugar de H1.
  6. crea otra clase oculta H3, basada en H2, y agrega la propiedad, o2, la almacena en el desplazamiento 1 en el objeto f1.
  7. actualizaciones f1 apuntan a H3.
  8. crea un objeto VariableEnvironment anónimo llamado a1.
  9. crear una clase oculta inicial de a1 llamada A1.
  10. indican que a1 padre es f1.

En la función de análisis literal, crea FunctionBody. Sólo cuando la función analizar FunctionBody era called.The siguiente código de indicar que no tira error al momento analizador

function p(){ 
    return function(){alert(a)} 
} 
p(); 

Así que en vez de GC H1, H2 irán a parar, porque no hay punto de referencia that.In mi mente si el código es compilado perezosamente, no hay forma de indicar que la variable declarada en a1 es una referencia a f1, usa JIT.

+0

V8 utilizó tanto Scavenge como Mark-Sweep-Compact, Scavenge es una referencia al algoritmo de Cheney http://en.wikipedia.org/wiki/Cheney%27s_algorithm –