Un artículo para comprender las fugas de memoria de JavaScript, los mecanismos de recolección de basura

你越是认真生活,你的生活就会越美好 —— Frank Lloyd Wright (documental sobre los frutos de la vida)

勤学如春起之苗,不见其增,日有所长;
辍学如磨刀之石,不见其损,日有所亏。 —— Tao Yuanming

1. ¿Qué es una pérdida de memoria?

Se requiere el funcionamiento del programa 内存. Siempre que el programa lo requiera, se debe proporcionar el sistema operativo o el tiempo de ejecución (tiempo de ejecución) 内存.

Para un funcionamiento continuo 服务进程(daemon), imprescindible 及时释放不再用到的内存. De lo contrario, el uso de la memoria aumentará cada vez más, lo que afectará el rendimiento del sistema o provocará que el proceso se bloquee.

Inserte la descripción de la imagen aquí

不再用到的内存,没有及时释放,就叫做内存泄漏(memory leak)

Algunos lenguajes (como el lenguaje C) deben liberar memoria manualmente y el programador es responsable de la gestión de la memoria.

char * buffer;
buffer = (char*) malloc(42);

// Do something with buffer

free(buffer);

El anterior es el C 语言código, que se malloc方法utiliza para solicitar memoria, después de usarlo, debe free方法liberar la memoria usted mismo .

Esto es muy problemático, por lo que la mayoría de los lenguajes proporcionan administración automática de memoria para reducir la carga del programador. Esto se llama " 垃圾回收机制" ( garbage collector).

Dos, mecanismo de recolección de basura

¿Cómo sabe el mecanismo de recolección de basura qué memoria ya no se necesita?

El método más utilizado se llama " 引用计数" (recuento de referencias): el motor de lenguaje tiene una "tabla de referencia" que guarda todos los recursos (normalmente varios valores) en la memoria 引用次数. Si el número de referencias a un valor es 0, significa que el valor ya no se utiliza, por lo que esta memoria se puede liberar.
Inserte la descripción de la imagen aquí

En la figura anterior, los dos valores de la esquina inferior izquierda no tienen referencias, por lo que pueden liberarse.

Si ya no se necesita un valor, pero el número de referencias no es 0, el mecanismo de recolección de basura no puede liberar esta memoria, lo que resultará 内存泄漏.

const arr = [1, 2, 3, 4];
console.log('hello world');

En el código anterior, la matriz [1, 2, 3, 4]es un valor y ocupará memoria. La variable arr es la única referencia a este valor, por lo que 引用次数es 1. Aunque el siguiente código no usa arr, todavía lo hace 持续占用内存.

Si agrega una línea de código, 解除arr对[1, 2, 3, 4]引用esta memoria se puede 垃圾回收机制liberar.

let arr = [1, 2, 3, 4];
console.log('hello world');
arr = null;

En el código anterior arr重置为null, se elimina la [1, 2, 3, 4]referencia al par , el número de referencias pasa a ser 0 y se puede liberar la memoria.

Por lo tanto, no significa que los 垃圾回收机制programadores se sentirán aliviados si lo han hecho. Aún debe prestar atención a la huella de memoria: una vez que los valores que ocupan espacio ya no se utilizan, debe verificar si todavía hay referencias a ellos. Si es así, debe eliminar la referencia manualmente.

En tercer lugar, el método de identificación de pérdidas de memoria.

¿Cómo se puede observar 内存泄漏?

La regla general es que si el 连续五次垃圾回收之后uso de memoria es mayor que una vez, lo habrá 内存泄漏. Esto requiere una visualización en tiempo real del uso de la memoria.

Navegador

ChromeVea en el navegador 内存占用y siga los pasos a continuación.
Inserte la descripción de la imagen aquí
PD: la imagen de abajo es el sistema de ventanas
Inserte la descripción de la imagen aquí

  1. Abrir 开发者工具, seleccionar Timelinepanel
  2. Compruebe en el campo de captura en la parte superiorMemory
  3. Haga clic en el botón de grabación en la esquina superior izquierda.
  4. Realice varias operaciones en la página para simular el uso del usuario.
  5. Después de un período de tiempo, haga clic en el botón de detener en el cuadro de diálogo y la 这段时间的内存占用situación se mostrará en el panel .

Si el uso de la memoria es básicamente estable y cercano al nivel, significa que no hay pérdida de memoria.
Inserte la descripción de la imagen aquí
Al contrario, es una pérdida de memoria.
Inserte la descripción de la imagen aquí

Línea de comando

La línea de comando puede utilizar el método Nodeproporcionado process.memoryUsage.

process.memoryUsage()

Inserte la descripción de la imagen aquí

process.memoryUsageDevuelve un objeto que contiene Node 进程的内存占用信息. El objeto contiene cuatro campos, la unidad es byte, el significado es el siguiente.
Inserte la descripción de la imagen aquí

  • rss(resident set size): Todo el uso de la memoria, incluido el área de instrucciones y la pila.
  • heapTotal: Memoria ocupada por "heap", incluidos los usados ​​y los no usados.
  • heapUsed: La parte del montón usado.
  • external: La memoria ocupada por los objetos C ++ dentro del motor V8.

判断内存泄漏,以heapUsed字段为准。

Cuatro, WeakMap

Como se mencionó anteriormente, es muy importante borrar las referencias a tiempo. Sin embargo, no puedes recordar tanto y, a veces, lo olvidas cuando lo descuidas, por lo que hay tantas pérdidas de memoria.

Es mejor tener una forma de declarar al crear una nueva referencia, qué referencias deben borrarse manualmente y qué referencias pueden ignorarse.Cuando otras referencias desaparecen, el mecanismo de recolección de basura puede liberar la memoria. Esto puede reducir en gran medida la carga del programador, siempre que borre la referencia principal.

ES6 toma esto en consideración e introduce dos tipos 新的数据结构: WeakSety WeakMap. Sus referencias a valores no se cuentan en el mecanismo de recolección de basura, por lo que habrá un "Débil" en el nombre para indicar que sí 弱引用.

A continuación WeakMap, por ejemplo, para ver cómo se resolvió 内存泄漏en el.

const wm = new WeakMap();

const element = document.getElementById('example');

wm.set(element, 'some information');
wm.get(element) // "some information"

Inserte la descripción de la imagen aquí

En el código anterior, cree uno nuevo primero Weakmap 实例. Luego, uno DOM 节点como 键名depósito para este ejemplo, y como información adicional 键值, almacenada en su WeakMapinterior. En este momento, la referencia WeakMapinterna elementes una referencia débil y no se contará en el mecanismo de recolección de basura.

En otras palabras, el recuento de referencias del objeto del nodo DOM es 1, no 2. En este momento, una vez eliminada la referencia al nodo, el mecanismo de recolección de basura liberará la memoria que ocupa. El par clave-valor guardado por Weakmap también desaparecerá automáticamente.

Básicamente, si desea agregar datos al objeto sin interferir con el mecanismo de recolección de basura, puede usarlo WeakMap.

Ejemplo de WeakMap

WeakMapEl ejemplo es difícil de demostrar, porque es imposible observar la referencia en su interior desaparecerá automáticamente. En este momento, se eliminan todas las demás referencias y ya no existe WeakMapel nombre de la clave al que apunta la referencia , lo que hace que sea imposible verificar si existe el nombre de la clave.

No pude pensar en una forma hasta que un día se me 贺师俊老师sugirió que si el valor señalado por la referencia ocupa mucha memoria, se puede ver a través del process.memoryUsagemétodo.

En base a esta idea, 网友 vtxfse agrega el siguiente ejemplo.
Primero, ábrelo Node 命令行.

node --expose-gc

En el código anterior, --expose-gc参数significa que está permitido 手动执行垃圾回收机制.

Luego, ejecute el siguiente código.

// 手动执行一次垃圾回收,保证获取的内存使用状态准确
> global.gc(); 
undefined

// 查看内存占用的初始状态,heapUsed 为 4M 左右
> process.memoryUsage(); 
{
    
     rss: 21106688,
  heapTotal: 7376896,
  heapUsed: 4153936,
  external: 9059 }

> let wm = new WeakMap();
undefined

> let b = new Object();
undefined

> global.gc();
undefined

// 此时,heapUsed 仍然为 4M 左右
> process.memoryUsage(); 
{
    
     rss: 20537344,
  heapTotal: 9474048,
  heapUsed: 3967272,
  external: 8993 }

// 在 WeakMap 中添加一个键值对,
// 键名为对象 b,键值为一个 5*1024*1024 的数组  
> wm.set(b, new Array(5*1024*1024));
WeakMap {
    
    }

// 手动执行一次垃圾回收
> global.gc();
undefined

// 此时,heapUsed 为 45M 左右
> process.memoryUsage(); 
{
    
     rss: 62652416,
  heapTotal: 51437568,
  heapUsed: 45911664,
  external: 8951 }

// 解除对象 b 的引用  
> b = null;
null

// 再次执行垃圾回收
> global.gc();
undefined

// 解除 b 的引用以后,heapUsed 变回 4M 左右
// 说明 WeakMap 中的那个长度为 5*1024*1024 的数组被销毁了
> process.memoryUsage(); 
{
    
     rss: 20639744,
  heapTotal: 8425472,
  heapUsed: 3979792,
  external: 8956 }

Inserte la descripción de la imagen aquí

En el código anterior, siempre que la referencia externa desaparezca, la referencia interna de WeakMap se borrará automáticamente mediante la recolección de basura. Esto demuestra que con su ayuda, resolver las pérdidas de memoria será mucho más sencillo.


谢谢你阅读到了最后~
期待你关注、收藏、评论、点赞~
让我们一起 变得更强


Enlace original
JavaScript Tutorial de fuga de memoria-Profesor Ruan Yifeng

Se recomienda leer el
alcance de JavaScript y la promoción de variables.
Aprendizaje del código fuente de Vue.

Supongo que te gusta

Origin blog.csdn.net/weixin_42752574/article/details/111088329
Recomendado
Clasificación