Vue 技术栈 教你玩"坏" v8引擎 吃透 js 内存回收机制

写在开头

学习完了ES 6基础,推荐阅读:ECMAScript 6 全套学习目录 整理 完结

现在开始逐步深入Vue 技术栈,想了想,技术栈专栏的主要内容包括:

1、Vue源码分析
2、手把手教 保姆级 撸代码
3、无惧面试,学以致用,继承创新
4、谈谈前端发展与学习心得
5、手写源码技术栈,附上详细注释
6、从源码中学习设计模式,一举两得
7、编程思想的提升及代码质量的提高
8、通过分析源码学习架构,看看优秀的框架
9、项目实战开发
10、面试准备,完善个人简历


暂时想到的就这么多,把这列举的10点做好了,我觉得也OK了,欢迎一起学习,觉得不错的话,可以关注博主,专栏会不断更新,可以关注一下,传送门~

学习目录

为了方便自己查阅与最后整合,还是打算整个目录,关于Vue技术栈前面的几篇优秀的文章:

Vue 技术栈 手写响应式原理 到 探索设计模式

正文

理解内存的意义


  • 防止页面占用内存过大,引起客户端卡顿,甚至无响应
  • node使用的也是v8引擎,内存对于后端服务的性能至关重要。因为服务的持久性,后端更容易造成内存溢出
  • 在面试官前秀一波操作,加深对你的印象,加分

v8引擎内存回收机制


v8的内存分配
内存大小
  • 和操作系统有关64位为1.4G,32位为0.7G
  • 64位下新生代的空间为64MB,老生代为1400MB
  • 32位下新生代的空间为16MB,老生代为700MB

why?

为什么内存大小要这样设计呢,读者可以先思考一下。

就现在来说的话,一般我们家用台式电脑、尤其是笔记本电脑,内存一般是4G或8G,当然有需求的人会去拓展,内存有这么大,为什么v8引擎不把内存限制提高一点呢?多一点好像也不成问题吧?

解惑:js作为一个脚本语言,设计之初就是为了服务浏览器,而浏览器作为前端有一个特点就是不持久化

因此,js代码的特点是执行一遍,几乎全部回收了,不像后端那样开启某一个服务,所有定义的全局变量一直存在。理论上说,1.4G已经够用了,俗话说:“杀鸡焉用宰牛刀”就是这个道理。

另外一个原因,就是js回收内存的时候,会暂停执行

var a=l;
var b=2;  //假设在执行定义b变量时发生了内存回收
//暂停执行
c();

回收一次100MB内存,大概需要6ms

当回收内存达到1GB时,大概需要1s的时间

因此,js的执行将会暂停1s,所以内存设置越大,可能执行一下就会卡顿一下,这肯定会影响用户的体验。(关于暂停执行这一块,下文会详细举例说明)

垃圾回收算法

我尽量用易懂的语言来讲解该算法的实现机制,关于具体算法的逻辑实现这里就不做详细探索了,有兴趣的读者可以开拓一下,引用优秀的文章。

  • 新生代简单来说就是先复制、用完直接删除
  • 老生代就是先标记删除、然后再整理

通过看图,我们能发现,新生代分成了两块。新生代内存空间是用来存放新产生的变量,一般是比较小,存在时间短的变量,而老生代如字面意思说存放新生代中旧的变量。

最开始,新的变量会放在From块中,当满足一定条件(下文会有介绍)后,就会将有用的变量复制一份到To块中,然后把From块全部清空。之后From变成了To,To变成了From,又将To块全部清空(可能这里有点绕,读者可以放慢阅读理解一下),然后就这样交替着将有用的变量进行转移。

那么,为什么在新生代要进行分块复制呢?学习过算法的读者应该知道,其中有两个重要指标:时间复杂度和空间复杂度,也是衡量一个算法优劣的标准,通过分块复制,可以用空间来换取时间,来对我们的算法进行优化。比如上述图示,当From有新变量时,To块是空着的,反过来,当To有新变量时,From块是空着的。

对于老生代,又采用了新的算法,因为新生代虽然牺牲了一般的空间,但是它总空间本来就不是很多,而老生代的空间要多好几倍,如果依旧采用该算法的话,那就很大程度地造成了资源的浪费,这并不是我们想要的结果。

老生代问题

由上图,来讲解一下关于老生代的问题,这里假设黑色部分是被标记需要删除的变量,那么在回收的时候会进行一个类似栈的操作,将需要删除的部分弹出去之后,后面的部分进行压栈进去。简单来说,就是黑色删除之后,白块往前挪动,这也是之前提及到的整理操作。这里类似于对电脑进行磁盘碎片整理的操作。 这样操作,也是为了保证内存连续性。

于是,就有了新的问题,为什么我们要保证内存的连续性

表面现象上内存感觉会更多了,但是深层去考虑的话一个原因是数组必须是连续的内存空间

//数组必须是连续的内存空间
var arr=[1,2,3,4];
假设连续内存:1000,1001,1002,1003

这里就好像我们一起去网吧开黑打撸一样,5个人当然是去找5排的位置…

新生代如何变成了老生代

上文,我们简单提及到了新生代当满足某一特定条件后就会成为老生代,下面我们进行详细探索:

通过上文,我们知道新生代多存放的是临时变量,如下左图所示,① 如果这个变量被回收过,那么就会晋升成为老生代。如下右图所示,② 看To空间是否使用了25%(以64位为例的话就是32MB的25%),因此一些占用内存比较大的变量会直接丢在老生代里去。

v8是如何处理变量的

理解如何处理内存,其实说白了就是理解如何处理我们的变量。

利用node来查看内存使用情况

关于内存使用情况,在浏览器上面,也是可以直接查看的,按F12,在控制台输入以下内容:

window.performance

就能看到我们的内存占用了
在这里插入图片描述
另外,你可以可以通过Performance选项进行查看

但我们肯定不会总是去浏览器去查看我们的内存使用情况的,这里推荐使用node调用api来查看我们的内存使用情况

  • 通过process.memoryUsage();
function getme(){
	var mem = process.memoryUsage();
	var format = function(bytes){
		return (bytes/1024/1024).toFixed(2)+'MB';
	};
	console.log('Process: heapTotal '+format(mem.heapTotal) +
		' heapUsed ' + format(mem.heapUsed) +
		' rss ' + format(mem.rss)
	);
};

以window10为例,进入我们node环境,输入process.memoryUsage(),查看下图,它会返回给我们一个对象,包含以下内容:

  • heapTotal 总内存
  • heapUsed 已使用内存
  • external 额外内存

    这里进行拓展一下,有学生说自己的内存拓展到了4G,其实拓展的并不是v8的内存,而是C++的内存

拓展知识:

node是C++写的,因此它有分配C++内存的能力。那么你可以给你的项目进行扩容,不过前提条件是node环境,浏览器的环境是不可以的

变量处理

变量主要分为两大类,全局变量和局部变量。

  • 内存主要就是存储变量等数据的
  • 全局对象会始终存活到程序运行结束
  • 局部变量当程序执行结束,且没有引用的时候就会消失

谈谈关于闭包问题:

疑问点:闭包会不会消失?

如果你使用了闭包,而且还一直处于引用状态,那么它就不会消失

疑问点:闭包会引起内存回收,让闭包内的变量一直得不到回收,造成了内存泄露?

这个说法其实是错误的,这个问题是IE5时代的一个bug,v8引擎一直在进步,基本上不存在这个问题。或许你是看了一本《JavaScript权威指南》那一本书,但是出版的挺早的了,那个时候还是IE的时代,所以闭包引起内存回收几乎不存在了,甚至你可以用闭包来消除全局变量。

教你如何玩"坏"v8引擎(起飞)

如下,我们定义了15个全局变量,每一个20MB,想想就会内存溢出,那么后面输出还能输出吗?

var size=20*1024*1024;	//20MB
var arr1=new Array(size);
var arr2=new Array(size);
var arr3=new Array(size);
var arr4=new Array(size);
var arr5=new Array(size);
var arr6=new Array(size);
var arr7=new Array(size);
var arr8=new Array(size);
var arr9=new Array(size);
var arr10=new Array(size);
var arr11=new Array(size);
var arr12=new Array(size);
var arr13=new Array(size);
var arr14=new Array(size);
var arr15=new Array(size);
console.log('能输出吗?');

执行:

首先,当我们输入指令后,还会卡几秒钟

读者也可以仔细找找,看看到底有没有输出我们想要的结果

<--- Last few GCs --->

[77876:000001F7424EADC0]     7482 ms: Mark-sweep 2081.4 (2114.3) -> 2081.4 (2083.3) MB, 2053.8 / 0.0 ms  (average mu = 0.124, current mu = 0.000) last resort GC in old space requested
[77876:000001F7424EADC0]     8645 ms: Mark-sweep 2081.4 (2083.3) -> 2081.4 (2083.3) MB, 1163.0 / 0.0 ms  (average mu = 0.074, current mu = 0.000) last resort GC in old space requested


<--- JS stacktrace --->

==== JS stack trace =========================================

    0: ExitFrame [pc: 00007FF7CD2E2C6D]
    1: ConstructFrame [pc: 00007FF7CD25DF0A]
    2: StubFrame [pc: 00007FF7CD343A90]
Security context: 0x02b1829008a1 <JSObject>
    3: /* anonymous */ [000002B18292DAA1] [G:\??????\Vue??\????\me.js:26] [bytecode=000002B1829307B9 offset=198](this=0x02b18292dbd1 <Object map = 0000018E79440431>,0x02b18292dbd1 <Object map = 0000018E79440431>,0x02b18292db91 <JSFunction require (sfi = 000002B182930E41)>,...

FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory
 1: 00007FF7CC71094F napi_wrap+124431
 2: 00007FF7CC6B2696 v8::base::CPU::has_sse+34502
 3: 00007FF7CC6B3356 v8::base::CPU::has_sse+37766
 4: 00007FF7CCEB6F4E v8::Isolate::ReportExternalAllocationLimitReached+94
 5: 00007FF7CCE9EF91 v8::SharedArrayBuffer::Externalize+833
 6: 00007FF7CCD6C85C v8::internal::Heap::EphemeronKeyWriteBarrierFromCode+1436
 7: 00007FF7CCD68890 v8::internal::Heap::AddRetainedMap+2608
 8: 00007FF7CCD8289E v8::internal::Factory::AllocateRawFixedArray+94
 9: 00007FF7CCD89C34 v8::internal::Factory::NewFixedArrayWithFiller+52
10: 00007FF7CCD89BF1 v8::internal::Factory::NewFixedArray+65
11: 00007FF7CCC5A98F v8::internal::FeedbackNexus::ic_state+56767
12: 00007FF7CCC6C6C5 v8::Message::GetIsolate+14101
13: 00007FF7CCC78600 v8::Message::GetIsolate+63056
14: 00007FF7CCC5438C v8::internal::FeedbackNexus::ic_state+30652
15: 00007FF7CCB1E8AA v8::internal::OrderedHashMap::ValueAt+62122
16: 00007FF7CD2E2C6D v8::internal::SetupIsolateDelegate::SetupHeap+567949
17: 00007FF7CD25DF0A v8::internal::SetupIsolateDelegate::SetupHeap+23850
18: 00007FF7CD343A90 v8::internal::SetupIsolateDelegate::SetupHeap+964784
19: 00007FF7CD2627FC v8::internal::SetupIsolateDelegate::SetupHeap+42524
20: 00007FF7CD2627FC v8::internal::SetupIsolateDelegate::SetupHeap+42524
21: 00007FF7CD2627FC v8::internal::SetupIsolateDelegate::SetupHeap+42524
22: 00007FF7CD2627FC v8::internal::SetupIsolateDelegate::SetupHeap+42524
23: 00007FF7CD2627FC v8::internal::SetupIsolateDelegate::SetupHeap+42524
24: 00007FF7CD2627FC v8::internal::SetupIsolateDelegate::SetupHeap+42524
25: 00007FF7CD2627FC v8::internal::SetupIsolateDelegate::SetupHeap+42524
26: 00007FF7CD25FDB1 v8::internal::SetupIsolateDelegate::SetupHeap+31697
27: 00007FF7CD25F99C v8::internal::SetupIsolateDelegate::SetupHeap+30652
28: 00007FF7CCDC4F43 v8::internal::Execution::CallWasm+1395
29: 00007FF7CCDC48C6 v8::internal::Execution::Call+182
30: 00007FF7CCE95B3B v8::Function::Call+603
31: 00007FF7CC6D95CE node::Start+1150
32: 00007FF7CC6D9877 node::Start+1831
33: 00007FF7CC6D874A node::LoadEnvironment+26
34: 00007FF7CC67B715 EVP_CIPHER_CTX_buf_noconst+30565
35: 00007FF7CC6D9263 node::Start+275
36: 00007FF7CC59666C RC4_options+339308
37: 00007FF7CD395D58 v8::internal::SetupIsolateDelegate::SetupHeap+1301368
38: 00007FFC37D77BD4 BaseThreadInitThunk+20
39: 00007FFC3864CEE1 RtlUserThreadStart+33

很明显,一个中文都没有,可能在13或者14样子的时候,内存不够了,然后想着去回收,但是发现没有变量能够回收,直接炸掉…

为了让读者更清晰看到里面隐藏的机制,我们边执行,边打印一下:

function getme(){
	var mem = process.memoryUsage();
	var format = function(bytes){
		return (bytes/1024/1024).toFixed(2)+'MB';
	};
	console.log('Process: heapTotal '+format(mem.heapTotal) +
		' heapUsed ' + format(mem.heapUsed) +
		' rss ' + format(mem.rss)
	);
};

var size=20*1024*1024;	//20MB
var arr1=new Array(size);
getme();
var arr2=new Array(size);
getme();
var arr3=new Array(size);
getme();
var arr4=new Array(size);
getme();
var arr5=new Array(size);
getme();
var arr6=new Array(size);
getme();
var arr7=new Array(size);
getme();
var arr8=new Array(size);
getme();
var arr9=new Array(size);
getme();
var arr10=new Array(size);
getme();
var arr11=new Array(size);
getme();
var arr12=new Array(size);
getme();
var arr13=new Array(size);
getme();
var arr14=new Array(size);
getme();
var arr15=new Array(size);
getme();
console.log('能输出吗?');

会打印如下结果:

Process: heapTotal 164.02MB heapUsed 161.97MB rss 177.51MB
Process: heapTotal 325.27MB heapUsed 322.19MB rss 338.86MB
Process: heapTotal 487.53MB heapUsed 482.22MB rss 499.32MB
Process: heapTotal 651.53MB heapUsed 642.19MB rss 659.49MB
Process: heapTotal 819.54MB heapUsed 802.19MB rss 819.87MB
Process: heapTotal 995.54MB heapUsed 962.19MB rss 980.53MB
Process: heapTotal 1155.54MB heapUsed 1122.20MB rss 1140.53MB
Process: heapTotal 1315.55MB heapUsed 1282.20MB rss 1300.54MB
Process: heapTotal 1475.55MB heapUsed 1442.20MB rss 1460.54MB
Process: heapTotal 1635.55MB heapUsed 1602.20MB rss 1620.55MB
Process: heapTotal 1795.56MB heapUsed 1762.20MB rss 1780.56MB
Process: heapTotal 1955.56MB heapUsed 1922.20MB rss 1940.69MB
Process: heapTotal 2115.57MB heapUsed 2081.72MB rss 2100.77MB

<--- Last few GCs --->

[79732:000001A9471C5D00]     5916 ms: Mark-sweep 2081.7 (2115.1) -> 2081.6 (2084.1) MB, 1132.9 / 0.0 ms  (average mu = 0.028, current mu = 0.000) last resort GC in old space requested
[79732:000001A9471C5D00]     6957 ms: Mark-sweep 2081.6 (2084.1) -> 2081.6 (2084.1) MB, 1041.3 / 0.0 ms  (average mu = 0.015, current mu = 0.000) last resort GC in old space requested


<--- JS stacktrace --->

==== JS stack trace =========================================

    0: ExitFrame [pc: 00007FF7CD2E2C6D]
    1: ConstructFrame [pc: 00007FF7CD25DF0A]
    2: StubFrame [pc: 00007FF7CD343A90]
Security context: 0x01178b3408a1 <JSObject>
    3: /* anonymous */ [000003AE3E898171] [G:\??????\Vue??\????\me.js:39] [bytecode=000002CAAA69A049 offset=243](this=0x03ae3e8982a1 <Object map = 0000025C5EA80431>,0x03ae3e8982a1 <Object map = 0000025C5EA80431>,0x03ae3e898261 <JSFunction require (sfi = 000002CAAA69A749)>,...

FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory
 1: 00007FF7CC71094F napi_wrap+124431
 2: 00007FF7CC6B2696 v8::base::CPU::has_sse+34502
 3: 00007FF7CC6B3356 v8::base::CPU::has_sse+37766
 4: 00007FF7CCEB6F4E v8::Isolate::ReportExternalAllocationLimitReached+94
 5: 00007FF7CCE9EF91 v8::SharedArrayBuffer::Externalize+833
 6: 00007FF7CCD6C85C v8::internal::Heap::EphemeronKeyWriteBarrierFromCode+1436
 7: 00007FF7CCD68890 v8::internal::Heap::AddRetainedMap+2608
 8: 00007FF7CCD8289E v8::internal::Factory::AllocateRawFixedArray+94
 9: 00007FF7CCD89C34 v8::internal::Factory::NewFixedArrayWithFiller+52
10: 00007FF7CCD89BF1 v8::internal::Factory::NewFixedArray+65
11: 00007FF7CCC5A98F v8::internal::FeedbackNexus::ic_state+56767
12: 00007FF7CCC6C6C5 v8::Message::GetIsolate+14101
13: 00007FF7CCC78600 v8::Message::GetIsolate+63056
14: 00007FF7CCC5438C v8::internal::FeedbackNexus::ic_state+30652
15: 00007FF7CCB1E8AA v8::internal::OrderedHashMap::ValueAt+62122
16: 00007FF7CD2E2C6D v8::internal::SetupIsolateDelegate::SetupHeap+567949
17: 00007FF7CD25DF0A v8::internal::SetupIsolateDelegate::SetupHeap+23850
18: 00007FF7CD343A90 v8::internal::SetupIsolateDelegate::SetupHeap+964784
19: 00007FF7CD2627FC v8::internal::SetupIsolateDelegate::SetupHeap+42524
20: 00007FF7CD2627FC v8::internal::SetupIsolateDelegate::SetupHeap+42524
21: 00007FF7CD2627FC v8::internal::SetupIsolateDelegate::SetupHeap+42524
22: 00007FF7CD2627FC v8::internal::SetupIsolateDelegate::SetupHeap+42524
23: 00007FF7CD2627FC v8::internal::SetupIsolateDelegate::SetupHeap+42524
24: 00007FF7CD2627FC v8::internal::SetupIsolateDelegate::SetupHeap+42524
25: 00007FF7CD2627FC v8::internal::SetupIsolateDelegate::SetupHeap+42524
26: 00007FF7CD25FDB1 v8::internal::SetupIsolateDelegate::SetupHeap+31697
27: 00007FF7CD25F99C v8::internal::SetupIsolateDelegate::SetupHeap+30652
28: 00007FF7CCDC4F43 v8::internal::Execution::CallWasm+1395
29: 00007FF7CCDC48C6 v8::internal::Execution::Call+182
30: 00007FF7CCE95B3B v8::Function::Call+603
31: 00007FF7CC6D95CE node::Start+1150
32: 00007FF7CC6D9877 node::Start+1831
33: 00007FF7CC6D874A node::LoadEnvironment+26
34: 00007FF7CC67B715 EVP_CIPHER_CTX_buf_noconst+30565
35: 00007FF7CC6D9263 node::Start+275
36: 00007FF7CC59666C RC4_options+339308
37: 00007FF7CD395D58 v8::internal::SetupIsolateDelegate::SetupHeap+1301368
38: 00007FFC37D77BD4 BaseThreadInitThunk+20
39: 00007FFC3864CEE1 RtlUserThreadStart+33

如上述代码所述,当内存定义到了第13个全局变量时,此时已经定义到了2GB左右多内存,发现后续还有变量需要开辟空间,于是想着要去回收一部分内存,此时发现之前全是全局变量,又炸了,因为全局变量会始终存活到程序运行结束,于是乎,只能报个错——“我太南了…”


这次就不玩坏它了,再玩可能心态就炸了,hhh。那么接下来看看是否如我们所想,会有一个回收的机制。从上文代码我们知道了,最多定义13个,不然会炸掉,那我们试试如下代码看看:

function getme(){
	var mem = process.memoryUsage();
	var format = function(bytes){
		return (bytes/1024/1024).toFixed(2)+'MB';
	};
	console.log('Process: heapTotal '+format(mem.heapTotal) +
		' heapUsed ' + format(mem.heapUsed) +
		' rss ' + format(mem.rss)
	);
};

var size=20*1024*1024;	//20MB
var a=[];
function b(){
	var arr1=new Array(size);
	var arr2=new Array(size);
	var arr3=new Array(size);
	var arr4=new Array(size);
	var arr5=new Array(size);
}
b();
for(var i=0;i<13;i++){
	a.push(new Array(size));
	getme();
}
console.log('能输出吗?');

执行结果:

分析一下:

对于局部变量呢,并不是说用完了一定就会回收,只是可以被回收

如上图执行结果所示,第一行输出了834.54MB,这里是定义全局变量的开始,但起初并不是为0,代表我们的局部变量并没有回收,那什么时候回收呢?我们看看下图:

诶,这里突然内存就变小了,显然就是我们局部变量被回收的时刻,因为此时已经达到了1.9Gb左右了,按上文看的话,2.1GB左右就会炸掉,于是乎,v8引擎就会往前看看有没有可回收的变量。然后就将没有引用过的局部变量收回了,防止内存炸掉。

由于这里不太方便演示输出过程,其实在内存回收的时候其实会有一点卡顿的感觉,这是必然的,为什么?

其实上文已经提及过,js回收时会暂停执行,于是会有一点卡顿的感觉,这里读者可以去实践体会一下。

如何注意内存的使用

优化内存的技巧
  • 尽量不要定义全局变量
  • 全局变量切记要销毁掉
  • 用匿名自执行函数变全局为局部
  • 尽量避免过多的引用闭包

回到之前的代码问题,定义了这么多的全局变量,内存肯定不够的。

var size=20*1024*1024;	//20MB

var arr1=new Array(size);
var arr2=new Array(size);
var arr3=new Array(size);
var arr4=new Array(size);
var arr5=new Array(size);
var arr6=new Array(size);
var arr7=new Array(size);
var arr8=new Array(size);
var arr9=new Array(size);
var arr10=new Array(size);
var arr11=new Array(size);
var arr12=new Array(size);
var arr13=new Array(size);
var arr14=new Array(size);
var arr15=new Array(size);
console.log('能输出吗?');

那么我们得要想着把没用的给删除掉,提到删除或许第一下想到的是delete操作,但是这里不推荐使用:

  • 支持性问题,浏览器可能会有不支持情况
  • 在严格模式下有bug

那么,最简单的方式就是使用了后,赋值为nullundefined

拓展知识:

nullundefined的在设计上的区别:

null其实是一个保留字,而undefined其实是一个变量

读到这里,可能会有读者不相信,来,我们去控制台跑一下就知道了:

保留字是不允许赋值的,而undefined你可想成一个比较特殊的变量

拓展就到这里了,回到上述问题,当每次用完后,我们进行删除操作后,看之前的代码是否还会炸掉:

var size=20*1024*1024;	//20MB

var arr1=new Array(size);
arr1=undefined;
var arr2=new Array(size);
arr2=undefined;
var arr3=new Array(size);
arr3=undefined;
var arr4=new Array(size);
arr4=undefined;
var arr5=new Array(size);
arr5=undefined;
var arr6=new Array(size);
arr6=undefined;
var arr7=new Array(size);
arr7=undefined;
var arr8=new Array(size);
arr8=undefined;
var arr9=new Array(size);
arr9=undefined;
var arr10=new Array(size);
arr10=undefined;
var arr11=new Array(size);
arr11=undefined;
var arr12=new Array(size);
arr12=undefined;
var arr13=new Array(size);
arr13=undefined;
var arr14=new Array(size);
arr14=undefined;
var arr15=new Array(size);
arr15=undefined;
console.log('能输出吗?');

执行结果:

但我们作为程序员工作当然不是一个人的事情,很多情况都是多人合作完成的,为了不给其它的同事造成影响,要养成一个好的习惯,比如我们可以使用匿名自执行函数变全局为局部

举个栗子:

var size=20*1024*1024;	//20MB
//匿名自执行函数
(function(){
	var arr1=new Array(size);
})()
防止内存泄漏
  • 滥用缓存
  • 大内存量操作

缓存是软件工程中非常伟大的发明,所有的优化可以用缓存来解决

缓存一般都是缓存在全局的。

继续拓展一下闭包问题:

我们所谓的闭包, 不是一种具体的写法,而是一种思想! 让变量在内部通过指定的方式给外部访问,而不是直接访问,这就是一个闭包,我们不能纠结于闭包怎么写怎么写。

手写node服务

假设写了一个后端访问,每次用户访问,我们将数据记录在a数组里:

var http=require('http');

var a=[];
var size=20*1024*1024;	//20MB
http.createServer(function(){
	function getme(){
		var mem = process.memoryUsage();
		var format = function(bytes){
			return (bytes/1024/1024).toFixed(2)+'MB';
		};
		console.log('Process: heapTotal '+format(mem.heapTotal) +
			' heapUsed ' + format(mem.heapUsed) +
			' rss ' + format(mem.rss)
		);
	};
	a.push(new Array(size));
	getme();
}).listen(3000); //配置3000端口

访问:http://localhost:3000/

上述只是我们第一次访问出现的结果,当我们不断地缓存,内存肯定会炸掉,那如何避免缓存滥用情况呢,下文继续探讨:

后端的同学可能就会想到Redis,对于前端而言,有localStorage

通过 缓存加锁 解决滥用问题

这里主要是对于使用v8引擎来说,原则上尽量不用v8去缓存大的数据,如果使用了v8来缓存的话,我们可通过缓存枷锁来解决这个问题。其实加锁并不是很复杂的问题,如下代码所示,当缓存长度大于4(真实数据根据实际情况而定),我们就将老的缓存清理掉

var size=20*1024*1024;	//20MB
var a=[];
for(var i=0;i<15;i++){
	if(a.length>4){
		a.shift();
	}
	a.push(new Array(size));
}
console.log('能输出吗?');

执行结果:

显然,我们加锁后,内存就不会炸掉了

避免大内存量操作

上文解决了内存泄漏的问题之一,滥用缓存问题,下文我们接着探索大内存量操作给我们内存带来的问题:

学过node的同学应该知道如下代码:

对于小文件读取当然不成问题,但如果有4-5个G呢?一次性读取会造成较大的问题

//node操作文件
//该api是一次性读取文件到buffer
fs.readFile();

因此,我们通常采用管道流来解决上述问题,通过管道送到可写流去(这里就牵扯到了操作系统方面的知识,读者可以继续研究探索)

createReadStream().pip(write);

讲完了node方面,在我们前端,其实也有相关问题,比如大文件上传,浏览器100%卡死,解决方法:切片上传

关于切片上传,本文只会简单概况,读者可以更深入研究,主要使用的就是自带的slice()函数,示例代码如下:

//js
//大文件上传问题
//切片上传
//file  slice(片)

post(file.slice(0,1000));
post(file.slice(1000,2000));

总结

学如逆水行舟,不进则退
发布了581 篇原创文章 · 获赞 1694 · 访问量 27万+

猜你喜欢

转载自blog.csdn.net/weixin_42429718/article/details/104734453