热搜:前端 nest neovim nvim

lxf2023-06-20 02:38:10

前言

最近在翻译外刊的时候,感觉很多好的文章翻译的时候不够地道,想为文章补充内容又担心准确度降低。莫得办法,最终我选择了以自己的大白话来总结一些翻译时的所学所想。

这篇文章将会根据我最近翻译的几篇node.js相关的垃圾回收机制,循序渐进 的进行一个JS相关的内存管理机制和垃圾回收的运行原理及介绍,文章没有特别深入,适合点杯奶茶☕搭配目录食用~

内存管理简介

众所周知,咱们开发的程序在跑的使用都是需要用到内存来存储一些数据的,但是要是 随便存,乱存,瞎xx存 那肯定存的少,因此在存的时候就会选择一些合适的数据结构,例如栈呀,堆呀,这里不展开讨论。

当然,存的多还不够,还得管的好。 那么就需要做内存管理 ,内存管理的目的是高效,快速的分配,并且在适当的时候释放和回收内存资源。内存管理功能提供了在程序请求时为 程序动态分配内存块 的方法,并在不再需要时 释放掉——这样内存就可以被重用。

JS中的内存管理

对于前端开发者而言,咱们的命根子是 js ,因此我们可能对内存管理这方面的认知弱于其他语言的开发者。咱们在日常开发的时候不会可以的去做这方面的考虑,这是为什么呢?

在应用程序级的内存管理中有两种方式,一种是手动的,一种是自动的。

以下是MDN的介绍:

像C语言这样的底层语言一般都有底层的内存管理接口,比如 malloc()free()。相反,JavaScript是在创建变量(对象,字符串等)时自动进行了分配内存,并且在不使用它们时“自动”释放。 释放的过程称为垃圾回收。这个“自动”是混乱的根源,并让JavaScript(和其他高级语言)开发者错误的感觉他们可以不关心内存管理。

C语言的手动管理内存分配如下例:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {

  char name[20];
  char *description;

  strcpy(name, "RisingStack");

  // memory allocation
  description = malloc( 30 * sizeof(char) );
   
  if( description == NULL ) {
     fprintf(stderr, "Error - unable to allocate required memory\n");
  } else {
     strcpy( description, "Trace by RisingStack is an APM.");
  }
  
  printf("Company name = %s\n", name );
  printf("Description: %s\n", description );

  // release memory
  free(description);
}

手动内存管理中,开发者需要自己去控制内存的分配与释放,如果你没有管理好内存就容易导致以下几种情况:

  • 当使用的内存空间从未被释放时,导致 内存泄漏
  • 删除对象时会出现 野/空指针,但该指针被重用。当其他数据结构被覆盖或敏感信息被读取时,可能会导致严重的安全问题。

科普一下,空指针是 指向任何的内存地址的指针,野指针是 指向一个非法的或已销毁的内存 的指针,幸运的是,js是一门自动内存管理的语言,无需咱们手动管理内存分配。

那么我们主要了解的就是自动内存管理,它到底是如何管理的呢?

自动内存管理机制

不管什么程序语言,内存生命周期基本是一致的:   

  1. 分配你所需要的内存
  2. 使用分配到的内存(读、写)
  3. 不需要时将其释放\归还

在JS中,我们在定义变量时会自动完成内存的分配,这一点应该很好理解,毕竟变量时咱们亲自创建的。但是光是创建可不行呀,咱们内存有限,有些变量要是没用了就得给他扬喽(销毁)。那js是如何销毁变量的呢?这里就涉及到了一个知识点——垃圾回收机制

垃圾回收机制

垃圾回收器简介

垃圾回收机制顾名思义就是将内存中没用的垃圾变量给回收了,但是关键点在于:怎么样的变量才是没用的变量捏?

高级语言解释器嵌入了“垃圾回收器”,它的主要工作是跟踪内存的分配和使用,以便当分配的内存不再使用时,自动释放它。这只能是一个近似的过程,因为要知道是否仍然需要某块内存是无法判定的(无法通过某种算法解决)。

JS中使用了垃圾回收器来进行回收变量,垃圾回收器的主要工作原理就是通过判断一个对象是否被引用,如果一个对象已经没有被任何其他对象引用(对象有没有其他对象引用到它),那么它就是一个可以回收的变量。

垃圾回收算法主要依赖于引用的概念。在内存管理的环境中,一个对象如果有访问另一个对象的权限(隐式或者显式),叫做一个对象引用另一个对象。例如,一个Javascript对象具有对它原型的引用(隐式引用)和对它属性的引用(显式引用)。

在这里,“对象”的概念不仅特指 JavaScript 对象,还包括函数作用域(或者全局词法作用域)。

以下是MDN的一个代码示例:

var o = {
  a: {
    b:2
  }
};
// 两个对象被创建,一个作为另一个的属性被引用,另一个被分配给变量o
// 很显然,没有一个可以被垃圾收集


var o2 = o; // o2变量是第二个对“这个对象”的引用

o = 1;      // 现在,“这个对象”只有一个o2变量的引用了,“这个对象”的原始引用o已经没有

var oa = o2.a; // 引用“这个对象”的a属性
               // 现在,“这个对象”有两个引用了,一个是o2,一个是oa

o2 = "yo"; // 虽然最初的对象现在已经是零引用了,可以被垃圾回收了
           // 但是它的属性a的对象还在被oa引用,所以还不能回收

oa = null; // a属性的那个对象现在也是零引用了
           // 它可以被垃圾回收了

变量引用图示

用译文中的一些图片展示一下可能会更加清晰:

下图显示了如果你有相互引用的对象和一些没有任何引用的对象的情况。

相关专题