摘要: JS是如何回收内存的?

《JavaScript深入浅出》系列:

最近垃圾回收这个话题非常火,大家不能随随便便的扔垃圾了,还得先分类,这样方便对垃圾进行回收再利用。

其实,对于写代码来说,也有垃圾回收(garbage collection)这个问题,这里所说的垃圾,指的是程序中不再需要的内存空间,垃圾回收指的是回收这些不再需要的内存空间,让程序可以重新利用这些释放的内存空间。

手动管理内存

对于C这种底层语言来说,我们可以使用malloc()函数分配内存空间,当所分配的内存不再需要的时候,可以使用free()函数来释放内存空间。

#include <stdio.h>
#include <stdlib.h>
#define TRUE 1 int main ()
{
int *p, i, n, sum; while (TRUE)
{
printf ("请输入数组长度: ");
scanf ("%d", &n);
p = (int *) malloc (n * sizeof (int)); // 分配内存空间
sum = 0;
for (i = 0; i < n; ++i)
{
*(p + i) = i + 1;
sum += *(p + i);
}
printf ("sum = %d\n", sum);
free (p); // 释放内存空间
}
return 0;
}

示例代码很简单,输入一个整数n,程序计算1、2、3...n的和。大家可以在Online C Compiler上运行这段代码。

请输入数组长度: 36
sum = 666
请输入数组长度: 100
sum = 5050

如果我们不去调用free()函数释放内存的话,就会导致内存泄漏(memory leak)。每个while循环中,指针p都会指向新分配的内存空间。而p之前指向的内存空间虽然没用了,但是并不会被释放,除非程序退出。如果while循环一直执行下去的话,内存早晚不够用。

垃圾回收算法

如果让我们去手动管理内存,那不知道要写出多少BUG,内存分分钟用完。还好现代编程语言,比如Java, Python, Go以及JavaScript,都是支持自动垃圾回收的。也就是说,这些语言可以自动回收程序不再需要的内存空间,这样既减轻了开发者的负担,也有效避免了内存泄漏。

其实,早在C语言诞生之前的1960年,图灵奖得主John McCarthy就在Lisp语言中实现了自动垃圾回收算法。算法本身其实非常简单,标记那些程序访问不到的数据,回收它们的内存空间。但是,垃圾回收算法把程序员从硬件层(内存管理)解放出来了,这种理念还是很先进的。

对于垃圾回收算法来说,最困难的问题是如何确定哪些内存空间是可以回收的,即哪些内存空间是程序不再需要的,这是一个不可判定问题(undecidable problem)。所谓不可判定,就是没有哪个垃圾回收算法可以确定程序中所有可以回收的内存空间。

McCarthy简化了判定数据是否需要的问题,将其简化为判断数据是否能够访问。如果程序已经不能访问某个数据了,那这个数据自然是不再需要了。但是,这个逻辑反过来是不成立的,一些可以访问的数据也有可能其实程序已经不再需要了。

McCarthy的垃圾回收算法现在通常被称作Mark-and-Sweep,它是现在很多语言(Java, JavaScript, Go)的垃圾回收算法的原型。

JavaScript的垃圾回收算法

对于JavaScript来说,我们是不需要手动管理内存的,因为JavaScript引擎例如V8与SpiderMonkey都会自动分配并回收内存。

比较古老的浏览器,比如IE6和IE7使用的垃圾回收算法是reference-counting:确定对象是否被引用,没有被引用的对象则可以回收。这个算法无法回收Circular Object,有可能会因此造成内存泄漏:

var div;
window.onload = function() {
div = document.getElementById('myDivElement');
div.circularReference = div;
div.lotsOfData = new Array(10000).join('*');
};

div对象的circularReference属性指向div本身,因此div对象始终“被引用”。如果使用reference-counting垃圾回收算法的话,则div对象永远不会被回收。最新的浏览器很早就不再使用reference-counting,因此Circular Object无法回收的问题也就不存在了。

目前,主流的浏览器使用的垃圾回收算法都是基于mark-and-sweep:

  • root对象包括全局对象以及程序当前的执行堆栈;
  • 从root对象,遍历其所有子对象,能够通过遍历访问到的对象是可以访问的;
  • 其他不能遍历对象是不可访问的,其内存空间可以回收;

算法思想并没有超越McCarthy半个世纪之前的设计,只是在实现细节上做了大量的优化,V8的垃圾回收模块Orinoco大致是这样做的:

  • 采用多线程的方式进行垃圾回收,尽量避免对JavaScript本身的代码执行造成暂停;
  • 利用浏览器渲染页面的空闲时间进行垃圾回收;
  • 根据The Generational Hypothesis,大多数对象的生命周期非常短暂,因此可以将对象根据生命周期进行区分,生命周期短的对象与生命周期长的对象采用不同的方式进行垃圾回收;
  • 对内存空间进行整理,消除内存碎片化,最大化利用释放的内存空间;

JS引擎的垃圾回收算法已经非常强大了,所以我们作为JavaScript开发者基本上感受不到它的存在。

观察JavaScript垃圾回收算法

我们通过Chrome开发者工具实际感受一下垃圾回收算法的效果。

测试1:

var str = new Array(100000000).join("*");

setInterval(() => {
console.log(str[0]);
}, 1000);

str是一个超长字符串,因此会占有不少的内存空间。代码里面写了一个setInterval,是为了让这段代码永远执行下去,程序不退出。这样的话,字符串str永远在使用中,永远是可以访问的,那它的内存空间就不会被回收。

我使用的是Chrome 75,在其开发者工具的Memory的Tab下,使用Take heap snapshot可以获取内存快照:

可知,内存占用了97MB,且我们可以在其中找到str这个超长字符串。

测试2

var str = new Array(100000000).join("*");

setInterval(() => {
console.log(str[0]);
}, 1000); setTimeout(() => {
str = "******";
}, 10000);

在setTimeout的回调函数中,我们对str进行了重新赋值,这就意味着之前的超长字符串就不可访问了,那它的内存空间就会被回收。

在代码运行10s之后,即str重新赋值之后进行快照:

可知,内存只占用了1.6MB,且我们可以在其中找到str字符串,它的长度只有6,因此占用的内存空间非常小。

想象一下,如果不再需要的内存空间不会被回收的话,1T的内存都不够用。

关于JS,我打算花1年时间写一个系列的博客JavaScript深入浅出,大家还有啥不太清楚的地方?不妨留言一下,我可以研究一下,然后再与大家分享一下。欢迎添加我的个人微信(KiwenLau),我是Fundebug的技术负责人,一个对JS又爱又恨的程序员。

参考

关于Fundebug

Fundebug专注于JavaScript、微信小程序、微信小游戏、支付宝小程序、React Native、Node.js和Java线上应用实时BUG监控。 自从2016年双十一正式上线,Fundebug累计处理了10亿+错误事件,付费客户有阳光保险、核桃编程、荔枝FM、掌门1对1、微脉、青团社等众多品牌企业。欢迎大家免费试用!

版权声明

转载时请注明作者 Fundebug以及本文地址:

https://blog.fundebug.com/2019/07/03/javascript-garbage-collection/

JavaScript深入浅出第3课:什么是垃圾回收算法?的更多相关文章

  1. JavaScript深入浅出第5课:Chrome是如何成功的?

    摘要: Chrome改变世界. <JavaScript深入浅出>系列: JavaScript深入浅出第1课:箭头函数中的this究竟是什么鬼? JavaScript深入浅出第2课:函数是一 ...

  2. JavaScript深入浅出第4课:V8引擎是如何工作的?

    摘要: 性能彪悍的V8引擎. <JavaScript深入浅出>系列: JavaScript深入浅出第1课:箭头函数中的this究竟是什么鬼? JavaScript深入浅出第2课:函数是一等 ...

  3. JavaScript深入浅出第2课:函数是一等公民是什么意思呢?

    摘要: 听起来很炫酷的一等公民是啥? <JavaScript深入浅出>系列: JavaScript深入浅出第1课:箭头函数中的this究竟是什么鬼? JavaScript深入浅出第2课:函 ...

  4. JavaScript深入浅出第1课:箭头函数中的this究竟是什么鬼?

    <JavaScript 深入浅出>系列: JavaScript 深入浅出第 1 课:箭头函数中的 this 究竟是什么鬼? JavaScript 深入浅出第 2 课:函数是一等公民是什么意 ...

  5. JAVA虚拟机垃圾回收算法原理

    除了释放不再被引用的对象外,垃圾收集器还要处理堆碎块.新的对象分配了空间,不再被引用的对象被释放,所以堆内存的空闲位置介于活动的对象之间.请求分配新对象时可能不得不增大堆空间的大小,虽然可以使用的总空 ...

  6. JVM调优-Jva中基本垃圾回收算法

    从不同的的角度去划分垃圾回收算法. 按照基本回收策略分 引用计数(Reference Counting) 比较古老的回收算法.原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计数.垃圾回 ...

  7. 深入理解java虚拟机【垃圾回收算法】

    Java虚拟机的内存区域中,程序计数器.虚拟机栈和本地方法栈三个区域是线程私有的,随线程生而生,随线程灭而灭:栈中的栈帧随着方法的进入和退出而进行入栈和出栈操作,每个栈帧中分配多少内存基本上是在类结构 ...

  8. 记录JVM垃圾回收算法

    垃圾回收算法可以分为三类,都基于标记-清除(复制)算法: Serial算法(单线程) 并行算法 并发算法 JVM会根据机器的硬件配置对每个内存代选择适合的回收算法,比如,如果机器多于1个核,会对年轻代 ...

  9. JVM 垃圾回收算法

    在说垃圾回收算法之前,先谈谈JVM怎样确定哪些对象是“垃圾”. 1.引用计数器算法: 引用计数器算法是给每个对象设置一个计数器,当有地方引用这个对象的时候,计数器+1,当引用失效的时候,计数器-1,当 ...

随机推荐

  1. Linux上用NAT实现上网

    1. 安装好Linux后,选择NAT方式 2. 在Windows主机上用ipconfig /all 查看VMnet8的IP地址,一般是192.168.X.1/255.255.255.0,如果不知道是哪 ...

  2. LeetCode 51. N-QueensN皇后 (C++)(八皇后问题)

    题目: The n-queens puzzle is the problem of placing n queens on an n×n chessboard such that no two que ...

  3. BZOJ 4491: 我也不知道题目名字是什么 线段树+离线

    code: #include <string> #include <cstring> #include <cstdio> #include <algorith ...

  4. 传统的DOM渲染方式

    什么是DOM渲染: DOM渲染是浏览器展现给用户的DOM文档的生成的过程. DOM渲染的演化过程: ①纯后端渲染 ②纯前端渲染 ③服务端的js渲染结合前端渲染 纯后端渲染:DOM树的生成完全是在后端服 ...

  5. [LeetCode] 189. Rotate Array 旋转数组

    Given an array, rotate the array to the right by k steps, where k is non-negative. Example 1: Input: ...

  6. [LeetCode] 19. Remove Nth Node From End of List 移除链表倒数第N个节点

    Given a linked list, remove the nth node from the end of list and return its head. For example, Give ...

  7. AntDeploy发布前端项目到IIS(脱离vs单独使用)

    AntDeploy AntDeploy是一款开源的一键发布部署工具,目的是代替重复性的发布动作,提高部署效率 1.一键部署iis 2.一键部署windows服务 3.一键部署到Docker 4.支持增 ...

  8. 024 如何让html引用公共的头部和尾部(多个html文件公用一个header.html和footer.html)

    前端静态html页面,封装公共的头文件(header:顶部页眉,顶部导航栏等部分)和尾部文件(footer:CopyRight.友情链接等部分) 当前方法:通过load()函数,引入公共头部和尾部文件 ...

  9. [Python学习笔记-008] 使用双向链表去掉重复的文本行

    用Python处理文本文件是极方便的,当文本文件中有较多的重复的行的时候,将那些重复的行数去掉并打印诸如"...<repeats X times>..."有助于更好的浏 ...

  10. Feign切换client到okhttp无法生效天坑!(附带发生的原因)

    提示:如果只看如何解决问题,请看文章的末尾如何解决这个问题 1. 场景描述 最近项目中使用了feign当做http请求工具来使用.相对于httpclient.resttemplate来说,fegin用 ...