The Node service built based on non-blocking and event-driven has the advantage of low memory consumption and is very suitable for processing massive network requests. Under the premise of massive requests, issues related to "memory control" need to be considered.
1. V8’s garbage collection mechanism and memory limitations
Js uses a garbage collection mechanism to perform automatic memory management, and developers do not need to do it like other languages ??( c/c), always pay attention to the allocation and release of memory during the process of writing code. In browsers, the garbage collection mechanism has little impact on application performance, but for performance-sensitive server-side programs, the quality of memory management and the quality of garbage collection will have an impact on services. [Related tutorial recommendations: nodejs video tutorial, Programming teaching]
1.1 Node and V8
Node is a Js runtime built on Chrome On the platform, V8 is the Js script engine of Node
1.2 Memory limitations of V8
In general back-end languages, there are no restrictions on basic memory usage, but in Node When Js uses memory, it can only use part of the memory. Under such restrictions, Node cannot directly operate large memory objects.
The main reason for the problem is that Node is built based on V8. The Js objects used in Node are basically allocated and managed through V8's own method.
1.3 V8 object allocation
In V8, all Js objects are allocated through the heap.
Check the memory usage in v8
heapTotal and heapUsed are the heap memory usage of V8. The former is the heap memory that has been applied for, and the latter is The amount currently used.
When a variable is declared and assigned a value in code, the memory of the object used is allocated on the heap. If the applied heap free memory is not enough to allocate new objects, heap memory will continue to be applied until the heap size exceeds the limit of V8
Why does V8 limit the size of the heap? The superficial reason is that V8 was originally designed for browsers As for the design, it is unlikely to encounter scenarios that use a large amount of memory. For web pages, V8's limit value is more than enough. The underlying reason is the limitation of V8's garbage collection mechanism. According to the official statement, taking 1.5G of garbage collection heap memory as an example, it takes more than 50 milliseconds for v8 to do a small garbage collection, and even more than 1 second to do a non-incremental garbage collection. This is the time during garbage collection that causes the JS thread to pause execution, and the application's performance and responsiveness will plummet. Directly limiting the heap memory is a good choice under the current considerations.
This restriction can be relaxed. When Node is started, you can pass --max-old-space-size
or --max-new-space-size
To adjust the size of the memory limit, once it takes effect, it cannot be changed dynamically. Such as:
node --max-old-space-size=1700 test.js // 單位為MB // 或者 node --max-new-space-size=1024 test.js // 單位為KB
1.4 V8’s garbage collection mechanism
Various garbage collection algorithms used in v8
1.4.1 V8’s main garbage collection algorithm
v8's garbage collection strategy is mainly based on the generational garbage collection mechanism.
In actual applications, the life cycle of objects varies. In the current garbage collection algorithm, memory garbage collection is performed in different generations according to the survival time of the object, and a more efficient algorithm is applied to the memory of different generations.
V8 memory generation
In v8, the memory is mainly divided into two generations: the new generation and the old generation. Objects in the young generation are objects with a short survival time, while objects in the old generation are objects with a long survival time or are resident in memory.
Memory space of the new generation Memory space of the old generation The overall size of the v8 heap = the memory space used by the new generation and the memory space used by the old generation
The maximum value of the v8 heap memory can only use about 1.4GB of memory under 64-bit systems and only about 1.4GB under 32-bit systems. Can use about 0.7GB of memory
Scavenge algorithm
On the basis of generations, objects in the new generation are mainly garbage collected through the Scavenge algorithm. The specific implementation of Scavenge mainly uses the Cheney algorithm. Cheney's algorithm is a garbage collection algorithm implemented by copying. Divide the heap memory into two parts, and each part of the space is called semispace. Of the two semispaces, only one is in use and the other is idle. The semispace space in use is called From space, and the space in idle state is called To space. When allocating an object, it is first allocated in the From space. When garbage collection starts, the surviving objects in the From space will be checked. These surviving objects will be copied to the To space, and the space occupied by non-surviving objects will be released. After the copying is completed, the roles of From space and To space are reversed. In short, during the garbage collection process, surviving objects are copied between two semispaces. The disadvantage of Scavenge is that it can only use half of the heap memory, which is determined by the partitioning space and copy mechanism. However, Scavenge has excellent performance in terms of time efficiency because it only copies surviving objects, and only a small portion of surviving objects are used in scenarios with short life cycles. Since Scavenge is a typical algorithm that sacrifices space for time, it cannot be applied to all garbage collections on a large scale. But Scavenge is very suitable for application in the new generation, because the life cycle of objects in the new generation is short and suitable for this algorithm.
The actual heap memory used is the sum of the two semispace spaces in the new generation and the memory size used in the old generation.
When an object still survives after multiple copies, it will be considered an object with a long life cycle, and will be moved to the old generation and managed using a new algorithm. The process of moving objects from the young generation to the old generation is called promotion.
In the simple Scavenge process, the surviving objects in the From space will be copied to the To space, and then the roles of the From space and the To space will be reversed (flip). However, under the premise of generational garbage collection, surviving objects in the From space need to be checked before being copied to the To space. Under certain conditions, objects with long survival periods need to be moved to the old generation, that is, object promotion is completed.
There are two main conditions for object promotion. One is whether the object has experienced Scavenge recycling, and the other is that the memory usage ratio of the To space exceeds the limit.
By default, V8’s object allocation is mainly concentrated in the From space. When an object is copied from the From space to the To space, its memory address will be checked to determine whether the object has experienced a Scavenge recycling. If it has been experienced, the object will be copied from the From space to the old generation space. If not, it will be copied to the To space. The promotion flow chart is as follows:
#Another judgment condition is the memory usage ratio of To space. When copying an object from the From space to the To space, if the To space has been used 25%, the object will be directly promoted to the old generation space. The promotion flow chart is as follows:
The reason for setting the 25% limit: When this Scavenge recycling is completed, this To space will become From space, and the next memory allocation will be carried out in this space. If the ratio is too high, it will affect subsequent memory allocation.
After the object is promoted, it will be treated as an object with a longer survival period in the old generation space and will be processed by the new recycling algorithm.
Mark-Sweep & Mark-Compact
For objects in the old generation, since surviving objects account for a large proportion, there are two problems if you use Scavenge: One is that there are many surviving objects, and the efficiency of copying surviving objects will be very low; the other problem is that half of the space is wasted. To this end, v8 mainly uses a combination of Mark-Sweep and Mark-Compact for garbage collection in the old generation.
Mark-Sweep means mark clearing, which is divided into two stages: marking and clearing. Compared with Scavenge, Mark-Sweep does not divide the memory space into two halves, so there is no behavior of wasting half of the space. Unlike Scavenge, which copies live objects, Mark-Sweep traverses all objects in the heap during the marking phase and marks the living objects. In the subsequent clearing phase, only unmarked objects are cleared. It can be found that Scavenge only copies living objects, while Mark-Sweep only cleans up dead objects. Live objects only account for a small part of the new generation, and dead objects only account for a small part of the old generation. This is the reason why the two recycling methods can handle it efficiently. The schematic diagram after Mark-Sweep is marked in the old generation space is as follows. The black parts are marked as dead objects
The biggest problem with Mark-Sweep is that after marking and clearing the space, the memory space will become discontinuous. This kind of memory fragmentation will cause problems for subsequent memory allocation. When a large object needs to be allocated, all the fragmented space will not be able to complete the allocation, and garbage collection will be triggered in advance, and this recycling is unnecessary.
Mark-Compact is to solve the memory fragmentation problem of Mark-Sweep. Mark-Compact means mark compilation, which evolved from Mark-Sweep. The difference is that after the object is marked as dead, during the cleaning process, the living objects are moved to one end. After the movement is completed, the memory outside the boundary is directly cleared. Schematic diagram after marking and moving living objects. The white grid is the living object, the dark grid is the dead object, and the light grid is the hole left after the living object is moved.
After completing the move, you can directly clear the memory area behind the rightmost surviving object to complete recycling.
In V8’s recycling strategy, Mark-Sweep and Mark-Compact are used in combination.
A simple comparison of three major garbage collection algorithms
Recycling algorithm | Mark-Sweep | Mark-Compact | Scavenge |
---|---|---|---|
Speed | Medium | Slowest | Fastest |
Space overhead | Less (with fragmentation) | Less (no fragmentation) | Double space (no fragmentation) |
Whether to move the object | No | Yes | Yes |
由于Mark-Compact需要移動(dòng)對(duì)象,所以它的執(zhí)行速度不可能很快,所以在取舍上,v8主要使用Mark-Sweep,在空間不足以對(duì)從新生代中晉升過(guò)來(lái)的對(duì)象進(jìn)行分配時(shí)才使用Mark-Compact
Incremental Marking
為了避免出現(xiàn)Js應(yīng)用邏輯與垃圾回收器看到的不一致情況,垃圾回收的3種基本算法都需要將應(yīng)用邏輯暫停下來(lái),待執(zhí)行完垃圾回收后再恢復(fù)執(zhí)行應(yīng)用邏輯,這種行為稱(chēng)為“全停頓”(stop-the-world).在v8的分代式垃圾回收中,一次小垃圾回收只收集新生代,由于新生代默認(rèn)配置得較小,且其中存活對(duì)象通常較少,所以即便它是全停頓的影響也不大。但v8的老生代通常配置得較大,且存活對(duì)象較多,全堆垃圾回收(full垃圾回收)的標(biāo)記、清理、整理等動(dòng)作造成的停頓就會(huì)比較可怕,需要設(shè)法改善
為了降低全堆垃圾回收帶來(lái)的停頓時(shí)間,v8先從標(biāo)記階段入手,將原本要一口氣停頓完成的動(dòng)作改為增量標(biāo)記(incremental marking),也就是拆分為許多小“步進(jìn)”,每做完一“步進(jìn)”,就讓Js應(yīng)用邏輯執(zhí)行一小會(huì)兒,垃圾回收與應(yīng)用邏輯交替執(zhí)行直到標(biāo)記階段完成。下圖為:增量標(biāo)記示意圖
v8在經(jīng)過(guò)增量標(biāo)記的改進(jìn)后,垃圾回收的最大停頓時(shí)間可以減少到原本的1/6左右。 v8后續(xù)還引入了延遲清理(lazy sweeping)與增量式整理(incremental compaction),讓清理與整理動(dòng)作也變成增量式的。同時(shí)還計(jì)劃引入標(biāo)記與并行清理,進(jìn)一步利用多核性能降低每次停頓的時(shí)間。
1.5 查看垃圾回收的日志
在啟動(dòng)時(shí)添加--trace_gc
參數(shù)。在進(jìn)行垃圾回收時(shí),將會(huì)從標(biāo)準(zhǔn)輸出中打印垃圾回收的日志信息。
node --trace_gc -e "var a = [];for (var i = 0; i < 1000000; i++) a.push(new Array(100));" > gc.log
在Node啟動(dòng)時(shí)使用--prof參數(shù),可以得到v8執(zhí)行時(shí)的性能分析數(shù)據(jù),包含了垃圾回收?qǐng)?zhí)行時(shí)占用的時(shí)間。以下面的代碼為例
// test.js for (var i = 0; i < 1000000; i++) { var a = {}; } node --prof test.js
會(huì)生成一個(gè)v8.log日志文件
2. 高效使用內(nèi)存
如何讓垃圾回收機(jī)制更高效地工作
2.1 作用域(scope)
在js中能形成作用域的有函數(shù)調(diào)用、with以及全局作用域
如下代碼:
var foo = function(){ var local = {}; }
foo()函數(shù)在每次被調(diào)用時(shí)會(huì)創(chuàng)建對(duì)應(yīng)的作用域,函數(shù)執(zhí)行結(jié)束后,該作用域會(huì)被銷(xiāo)毀。同時(shí)作用域中聲明的局部變量分配在該作用域上,隨作用域的銷(xiāo)毀而銷(xiāo)毀。只被局部變量引用的對(duì)象存活周期較短。在這個(gè)示例中,由于對(duì)象非常小,將會(huì)被分配在新生代中的From空間中。在作用域釋放后,局部變量local失效,其引用的對(duì)象將會(huì)在下次垃圾回收時(shí)被釋放
2.1.1 標(biāo)識(shí)符查找
標(biāo)識(shí)符,可以理解為變量名。下面的代碼,執(zhí)行bar()函數(shù)時(shí),將會(huì)遇到local變量
var bar = function(){ console.log(local); }
js執(zhí)行時(shí)會(huì)查找該變量定義在哪里。先查找的是當(dāng)前作用域,如果在當(dāng)前作用域無(wú)法找到該變量的聲明,會(huì)向上級(jí)的作用域里查找,直到查到為止。
2.1.2作用域鏈
在下面的代碼中
var foo = function(){ var local = 'local var'; var bar = function(){ var local = 'another var'; var baz = function(){ console.log(local) }; baz() } bar() } foo()
baz()函數(shù)中訪問(wèn)local變量時(shí),由于作用域中的變量列表中沒(méi)有l(wèi)ocal,所以會(huì)向上一個(gè)作用域中查找,接著會(huì)在bar()函數(shù)執(zhí)行得到的變量列表中找到了一個(gè)local變量的定義,于是使用它。盡管在再上一層的作用域中也存在local的定義,但是不會(huì)繼續(xù)查找了。如果查找一個(gè)不存在的變量,將會(huì)一直沿著作用域鏈查找到全局作用域,最后拋出未定義錯(cuò)誤。
2.1.3 變量的主動(dòng)釋放
如果變量是全局變量(不通過(guò)var聲明或定義在global變量上),由于全局作用域需要直到進(jìn)程退出才能釋放,此時(shí)將導(dǎo)致引用的對(duì)象常駐內(nèi)存(常駐在老生代中)。如果需要釋放常駐內(nèi)存的對(duì)象,可以通過(guò)delete操作來(lái)刪除引用關(guān)系?;蛘邔⒆兞恐匦沦x值,讓舊的對(duì)象脫離引用關(guān)系。在接下來(lái)的老生代內(nèi)存清除和整理的過(guò)程中,會(huì)被回收釋放。示例代碼如下:
global.foo = "I am global object" console.log(global.foo);// => "I am global object" delete global.foo; // 或者重新賦值 global.foo = undefined; console.log(global.foo); // => undefined
雖然delete操作和重新賦值具有相同的效果,但是在V8中通過(guò)delete刪除對(duì)象的屬性有可能干擾v8的優(yōu)化,所以通過(guò)賦值方式解除引用更好。
2.2 閉包
作用域鏈上的對(duì)象訪問(wèn)只能向上,外部無(wú)法向內(nèi)部訪問(wèn)。
js實(shí)現(xiàn)外部作用域訪問(wèn)內(nèi)部作用域中變量的方法叫做閉包。得益于高階函數(shù)的特性:函數(shù)可以作為參數(shù)或者返回值。
var foo = function(){ var bar = function(){ var local = "局部變量"; return function(){ return local; } } var baz = bar() console.log(baz()) }
在bar()函數(shù)執(zhí)行完成后,局部變量local將會(huì)隨著作用域的銷(xiāo)毀而被回收。但是這里返回值是一個(gè)匿名函數(shù),且這個(gè)函數(shù)中具備了訪問(wèn)local的條件。雖然在后續(xù)的執(zhí)行中,在外部作用域中還是無(wú)法直接訪問(wèn)local,但是若要訪問(wèn)它,只要通過(guò)這個(gè)中間函數(shù)稍作周轉(zhuǎn)即可。
閉包是js的高級(jí)特性,利用它可以產(chǎn)生很多巧妙的效果。它的問(wèn)題在于,一旦有變量引用這個(gè)中間函數(shù),這個(gè)中間函數(shù)將不會(huì)釋放,同時(shí)也會(huì)使原始的作用域不會(huì)得到釋放,作用域中產(chǎn)生的內(nèi)存占用也不會(huì)得到釋放。
無(wú)法立即回收的內(nèi)存有閉包和全局變量引用這兩種情況。由于v8的內(nèi)存限制,要注意此變量是否無(wú)限制地增加,會(huì)導(dǎo)致老生代中的對(duì)象增多。
3.內(nèi)存指標(biāo)
會(huì)存在一些認(rèn)為會(huì)回收但是卻沒(méi)有被回收的對(duì)象,會(huì)導(dǎo)致內(nèi)存占用無(wú)限增長(zhǎng)。一旦增長(zhǎng)達(dá)到v8的內(nèi)存限制,將會(huì)得到內(nèi)存溢出錯(cuò)誤,進(jìn)而導(dǎo)致進(jìn)程退出。
3.1 查看內(nèi)存使用情況
process.memoryUsage()可以查看內(nèi)存使用情況。除此之外,os模塊中的totalmem()和freemem()方法也可以查看內(nèi)存使用情況
3.1.1 查看進(jìn)程的內(nèi)存使用
調(diào)用process.memoryUsage()可以看到Node進(jìn)程的內(nèi)存占用情況
rss是resident set size的縮寫(xiě),即進(jìn)程的常駐內(nèi)存部分。進(jìn)程的內(nèi)存總共有幾部分,一部分是rss,其余部分在交換區(qū)(swap)或者文件系統(tǒng)(filesystem)中。
除了rss外,heapTotal和heapUsed對(duì)應(yīng)的是v8的堆內(nèi)存信息。heapTotal是堆中總共申請(qǐng)的內(nèi)存量,heapUsed表示目前堆中使用中的內(nèi)存量。單位都是字節(jié)。示例如下:
var showMem = function () { 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)) console.log('---------------------') } var useMem = function () { var size = 50 * 1024 * 1024; var arr = new Array(size); for (var i = 0; i < size; i++) { arr[i] = 0 } return arr } var total = [] for (var j = 0; j < 15; j++) { showMem(); total.push(useMem()) } showMem();
在內(nèi)存達(dá)到最大限制值的時(shí)候,無(wú)法繼續(xù)分配內(nèi)存,然后進(jìn)程內(nèi)存溢出了。
3.1.2 查看系統(tǒng)的內(nèi)存占用
os模塊中的totalmem()和freemem()這兩個(gè)方法用于查看操作系統(tǒng)的內(nèi)存使用情況,分別返回系統(tǒng)的總內(nèi)存和閑置內(nèi)存,以字節(jié)為單位
3.2 堆外內(nèi)存
通過(guò)process.memoryUsage()的結(jié)果可以看到,堆中的內(nèi)存用量總是小于進(jìn)程的常駐內(nèi)存用量,意味著Node中的內(nèi)存使用并非都是通過(guò)v8進(jìn)行分配的。將那些不是通過(guò)v8分配的內(nèi)存稱(chēng)為堆外內(nèi)存
將上面的代碼里的Array變?yōu)锽uffer,將size變大
var useMem = function () { var size = 200 * 1024 * 1024; var buffer = Buffer.alloc(size); // new Buffer(size)是舊語(yǔ)法 for (var i = 0; i < size; i++) { buffer[i] = 0 } return buffer }
輸出結(jié)果如下:
內(nèi)存沒(méi)有溢出,改造后的輸出結(jié)果中,heapTotal與heapUsed的變化極小,唯一變化的是rss的值,并且該值已經(jīng)遠(yuǎn)遠(yuǎn)超過(guò)v8的限制值。原因是Buffer對(duì)象不同于其它對(duì)象,它不經(jīng)過(guò)v8的內(nèi)存分配機(jī)制,所以也不會(huì)有堆內(nèi)存的大小限制。意味著利用堆外內(nèi)存可以突破內(nèi)存限制的問(wèn)題
Node的內(nèi)存主要由通過(guò)v8進(jìn)行分配的部分和Node自行分配的部分構(gòu)成。受v8的垃圾回收限制的只要是v8的堆內(nèi)存。
4. 內(nèi)存泄漏
Node對(duì)內(nèi)存泄漏十分敏感,內(nèi)存泄漏造成的堆積,垃圾回收過(guò)程中會(huì)耗費(fèi)更多的時(shí)間進(jìn)行對(duì)象掃描,應(yīng)用響應(yīng)緩慢,直到進(jìn)程內(nèi)存溢出,應(yīng)用崩潰。
在v8的垃圾回收機(jī)制下,大部分情況是不會(huì)出現(xiàn)內(nèi)存泄漏的,但是內(nèi)存泄漏通常產(chǎn)生于無(wú)意間,排查困難。內(nèi)存泄漏的情況不盡相同,但本質(zhì)只有一個(gè),那就是應(yīng)當(dāng)回收的對(duì)象出現(xiàn)意外而沒(méi)有被回收,變成了常駐在老生代中的對(duì)象。通常原因有如下幾個(gè):
- 緩存
- 隊(duì)列消費(fèi)不及時(shí)
- 作用域未釋放
4.1 慎將內(nèi)存當(dāng)緩存用
緩存在應(yīng)用中的作用十分重要,可以十分有效地節(jié)省資源。因?yàn)樗脑L問(wèn)效率要比 I/O 的效率高,一旦命中緩存,就可以節(jié)省一次 I/O時(shí)間。
對(duì)象被當(dāng)作緩存來(lái)使用,意味著將會(huì)常駐在老生代中。緩存中存儲(chǔ)的鍵越多,長(zhǎng)期存活的對(duì)象也就越多,導(dǎo)致垃圾回收在進(jìn)行掃描和整理時(shí),對(duì)這些對(duì)象做無(wú)用功。
Js開(kāi)發(fā)者喜歡用對(duì)象的鍵值對(duì)來(lái)緩存東西,但這與嚴(yán)格意義上的緩存又有著區(qū)別,嚴(yán)格意義的緩存有著完善的過(guò)期策略,而普通對(duì)象的鍵值對(duì)并沒(méi)有。是一種以?xún)?nèi)存空間換CPU執(zhí)行時(shí)間。示例代碼如下:
var cache = {}; var get = function (key) { if (cache[key]) { return cache[key]; } else { // get from otherwise } }; var set = function (key, value) { cache[key] = value; };
所以在Node中,拿內(nèi)存當(dāng)緩存的行為應(yīng)當(dāng)被限制。當(dāng)然,這種限制并不是不允許使用,而是要小心為之。
4.1.1 緩存限制策略
為了解決緩存中的對(duì)象永遠(yuǎn)無(wú)法釋放的問(wèn)題,需要加入一種策略來(lái)限制緩存的無(wú)限增長(zhǎng)??梢詫?shí)現(xiàn)對(duì)鍵值數(shù)量的限制。下面是其實(shí)現(xiàn):
var LimitableMap = function (limit) { this.limit = limit || 10; this.map = {}; this.keys = []; }; var hasOwnProperty = Object.prototype.hasOwnProperty; LimitableMap.prototype.set = function (key, value) { var map = this.map; var keys = this.keys; if (!hasOwnProperty.call(map, key)) { if (keys.length === this.limit) { var firstKey = keys.shift(); delete map[firstKey]; } keys.push(key); } map[key] = value; }; LimitableMap.prototype.get = function (key) { return this.map[key]; }; module.exports = LimitableMap;
記錄鍵在數(shù)組中,一旦超過(guò)數(shù)量,就以先進(jìn)先出的方式進(jìn)行淘汰。
4.1.2 緩存的解決方案
直接將內(nèi)存作為緩存的方案要十分慎重。除了限制緩存的大小外,另外要考慮的事情是,進(jìn)程之間無(wú)法共享內(nèi)存。如果在進(jìn)程內(nèi)使用緩存,這些緩存不可避免地有重復(fù),對(duì)物理內(nèi)存的使用是一種浪費(fèi)。
如何使用大量緩存,目前比較好的解決方案是采用進(jìn)程外的緩存,進(jìn)程自身不存儲(chǔ)狀態(tài)。外部的緩存軟件有著良好的緩存過(guò)期淘汰策略以及自有的內(nèi)存管理,不影響Node進(jìn)程的性能。它的好處多多,在Node中主要可以解決以下兩個(gè)問(wèn)題。
- 將緩存轉(zhuǎn)移到外部,減少常駐內(nèi)存的對(duì)象的數(shù)量,讓垃圾回收更高效。
- 進(jìn)程之間可以共享緩存。
目前,市面上較好的緩存有Redis和Memcached。
4.2 關(guān)注隊(duì)列狀態(tài)
隊(duì)列在消費(fèi)者-生產(chǎn)者模型中經(jīng)常充當(dāng)中間產(chǎn)物。這是一個(gè)容易忽略的情況,因?yàn)樵诖蠖鄶?shù)應(yīng)用場(chǎng)景下,消費(fèi)的速度遠(yuǎn)遠(yuǎn)大于生產(chǎn)的速度,內(nèi)存泄漏不易產(chǎn)生。但是一旦消費(fèi)速度低于生產(chǎn)速度,將會(huì)形成堆積, 導(dǎo)致Js中相關(guān)的作用域不會(huì)得到釋放,內(nèi)存占用不會(huì)回落,從而出現(xiàn)內(nèi)存泄漏。
解決方案應(yīng)該是監(jiān)控隊(duì)列的長(zhǎng)度,一旦堆積,應(yīng)當(dāng)通過(guò)監(jiān)控系統(tǒng)產(chǎn)生報(bào)警并通知相關(guān)人員。另一個(gè)解決方案是任意異步調(diào)用都應(yīng)該包含超時(shí)機(jī)制,一旦在限定的時(shí)間內(nèi)未完成響應(yīng),通過(guò)回調(diào)函數(shù)傳遞超時(shí)異常,使得任意異步調(diào)用的回調(diào)都具備可控的響應(yīng)時(shí)間,給消費(fèi)速度一個(gè)下限值。
5. 內(nèi)存泄漏排查
常見(jiàn)的工具
- v8-profiler: 可以用于對(duì)V8堆內(nèi)存抓取快照和對(duì)CPU進(jìn)行分析
- node-heapdump: 允許對(duì)V8堆內(nèi)存抓取快照,用于事后分析
- node-mtrace: 使用了GCC的mtrace工具來(lái)分析堆的使用
- dtrace:有完善的dtrace工具用來(lái)分析內(nèi)存泄漏
- node-memwatch
6. 大內(nèi)存應(yīng)用
由于Node的內(nèi)存限制,操作大文件也需要小心,好在Node提供了stream模塊用于處理大文件。
stream模塊是Node的原生模塊,直接引用即可。stream繼承自EventEmitter,具備基本的自定義事件功能,同時(shí)抽象出標(biāo)準(zhǔn)的事件和方法。它分可讀和可寫(xiě)兩種。Node中的大多數(shù)模塊都有stream的應(yīng)用,比如fs的createReadStream()和createWriteStream()方法可以分別用于創(chuàng)建文件的可讀流和可寫(xiě)流,process模塊中的stdin和stdout則分別是可讀流和可寫(xiě)流的示例。
由于V8的內(nèi)存限制,我們無(wú)法通過(guò)fs.readFile()和fs.writeFile()直接進(jìn)行大文件的操作,而改用fs.createReadStream()和fs.createWriteStream()方法通過(guò)流的方式實(shí)現(xiàn)對(duì)大文件的操作。下面的代碼展示了如何讀取一個(gè)文件,然后將數(shù)據(jù)寫(xiě)入到另一個(gè)文件的過(guò)程:
var reader = fs.createReadStream('in.txt'); var writer = fs.createWriteStream('out.txt'); reader.on('data', function (chunk) { writer.write(chunk); }); reader.on('end', function () { writer.end(); }); // 簡(jiǎn)潔的方式 var reader = fs.createReadStream('in.txt'); var writer = fs.createWriteStream('out.txt'); reader.pipe(writer);
可讀流提供了管道方法pipe(),封裝了data事件和寫(xiě)入操作。通過(guò)流的方式,上述代碼不會(huì)受到V8內(nèi)存限制的影響,有效地提高了程序的健壯性。
如果不需要進(jìn)行字符串層面的操作,則不需要借助V8來(lái)處理,可以嘗試進(jìn)行純粹的Buffer操作,這不會(huì)受到V8堆內(nèi)存的限制。但是這種大片使用內(nèi)存的情況依然要小心,即使V8不限制堆內(nèi)存的大小,物理內(nèi)存依然有限制。
更多node相關(guān)知識(shí),請(qǐng)?jiān)L問(wèn):nodejs 教程!
The above is the detailed content of An article about memory control in Node. For more information, please follow other related articles on the PHP Chinese website!

Hot AI Tools

Undress AI Tool
Undress images for free

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Clothoff.io
AI clothes remover

Video Face Swap
Swap faces in any video effortlessly with our completely free AI face swap tool!

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics

PHP and Vue: a perfect pairing of front-end development tools. In today's era of rapid development of the Internet, front-end development has become increasingly important. As users have higher and higher requirements for the experience of websites and applications, front-end developers need to use more efficient and flexible tools to create responsive and interactive interfaces. As two important technologies in the field of front-end development, PHP and Vue.js can be regarded as perfect tools when paired together. This article will explore the combination of PHP and Vue, as well as detailed code examples to help readers better understand and apply these two

As a fast and efficient programming language, Go language is widely popular in the field of back-end development. However, few people associate Go language with front-end development. In fact, using Go language for front-end development can not only improve efficiency, but also bring new horizons to developers. This article will explore the possibility of using the Go language for front-end development and provide specific code examples to help readers better understand this area. In traditional front-end development, JavaScript, HTML, and CSS are often used to build user interfaces

Django is a web application framework written in Python that emphasizes rapid development and clean methods. Although Django is a web framework, to answer the question whether Django is a front-end or a back-end, you need to have a deep understanding of the concepts of front-end and back-end. The front end refers to the interface that users directly interact with, and the back end refers to server-side programs. They interact with data through the HTTP protocol. When the front-end and back-end are separated, the front-end and back-end programs can be developed independently to implement business logic and interactive effects respectively, and data exchange.

As a C# developer, our development work usually includes front-end and back-end development. As technology develops and the complexity of projects increases, the collaborative development of front-end and back-end has become more and more important and complex. This article will share some front-end and back-end collaborative development techniques to help C# developers complete development work more efficiently. After determining the interface specifications, collaborative development of the front-end and back-end is inseparable from the interaction of API interfaces. To ensure the smooth progress of front-end and back-end collaborative development, the most important thing is to define good interface specifications. Interface specification involves the name of the interface

In front-end development interviews, common questions cover a wide range of topics, including HTML/CSS basics, JavaScript basics, frameworks and libraries, project experience, algorithms and data structures, performance optimization, cross-domain requests, front-end engineering, design patterns, and new technologies and trends. . Interviewer questions are designed to assess the candidate's technical skills, project experience, and understanding of industry trends. Therefore, candidates should be fully prepared in these areas to demonstrate their abilities and expertise.

Methods for implementing instant messaging include WebSocket, Long Polling, Server-Sent Events, WebRTC, etc. Detailed introduction: 1. WebSocket, which can establish a persistent connection between the client and the server to achieve real-time two-way communication. The front end can use the WebSocket API to create a WebSocket connection and achieve instant messaging by sending and receiving messages; 2. Long Polling, a technology that simulates real-time communication, etc.

Django: A magical framework that can handle both front-end and back-end development! Django is an efficient and scalable web application framework. It is able to support multiple web development models, including MVC and MTV, and can easily develop high-quality web applications. Django not only supports back-end development, but can also quickly build front-end interfaces and achieve flexible view display through template language. Django combines front-end development and back-end development into a seamless integration, so developers don’t have to specialize in learning

Combination of Golang and front-end technology: To explore how Golang plays a role in the front-end field, specific code examples are needed. With the rapid development of the Internet and mobile applications, front-end technology has become increasingly important. In this field, Golang, as a powerful back-end programming language, can also play an important role. This article will explore how Golang is combined with front-end technology and demonstrate its potential in the front-end field through specific code examples. The role of Golang in the front-end field is as an efficient, concise and easy-to-learn
