This is the 8th day of my participation in the November Gwen Challenge. Check out the event details: The last Gwen Challenge 2021

Efficient memory use

Previously, Memory Control (PART 1), V8 Memory Allocation

All that has been said about v8’s garbage collection is that developers need to make it more efficient

scope

The first thing to do to trigger garbage collection is scope.

A scope is essentially the effective scope of a variable or function. A scope controls the visibility and life cycle of a variable or function

Current scopes are divided into:

  1. Global scope
  2. Function scope
  3. Block-level scope

Local variables declared in a scope are assigned to that scope and are destroyed when the scope is destroyed. After scoped release, objects referenced by local variables will be released in the next garbage collection.

Global scope

Objects in this scope can be accessed anywhere, and their life cycles follow the life of the page

The following are global scoped

  • Properties on window (in browser)window.location
  • The outermost function definedfunction add(){}
  • Outermost variable definedvar k = 5
  • A variable that does not define a direct assignmenta = 10

Function scope

A variable or function defined inside a function

  • A function creates its scope each time it is called, and the scope is destroyed when the function completes execution.
  • Local variables declared in a scope are assigned to that scope and are destroyed when the scope is destroyed
  • A defined variable or function can only be accessed from within the function itself

Example: A defined in foo is in foo’s function scope

var foo = function() {
  var a = 5;
}
Copy the code

Block-level scope

Before ES6, there was no concept of a block. After ES6, block-level scopes were introduced. Concepts such as let and const are also added

Wherever there are blocks of code there are block-level scopes (wrapped by {}, (), etc., and variables that need to be declared by let or const)

Block-level scopes and function scopes are not

Block-level scoped variables are not promoted to the top of the code block

Example:

Uncaught ReferenceError: Uncaught ReferenceError: a: undefined; b: Uncaught ReferenceError Cannot access ‘b’ before Initialization because the variables defined by let are block-level scoped

(function test() {
    console.log(a);
    console.log(b);
    var a = 5;
    let b = 5; }) ();Copy the code

And no duplicate declarations are allowed in the block-level scope.

Example:

(function test() {
    var a = 5;
    let a = 10;  // Uncaught SyntaxError: Identifier 'a' has already been declared}) ();Copy the code
(function test() {
    var a = 5;
    {
      let a = 10;  
    }
})();
Copy the code

Knowledge of scope

  1. Identifier lookup

Identifiers can be understood as variable names.

When executed, JavaScript looks for the definition of the variable in the current scope first. If it cannot find the declaration of the variable in the current scope, it looks for the declaration in the higher scope until it finds it.

  1. The scope chain

The chain of scope is formed as the identifiers are searched one layer at a time.

If no variable declaration corresponding to the identifier is found in the global scope along the scope chain, an undefined error is thrown.

  1. Active release of variables

If the variable is global, since the global scope needs to be released until the process exits, the referenced object will be resident in memory (resident in the old generation).

In JavaScript, you can use the delete keyword to manually delete variables, allowing the garbage collection to reclaim memory

However, the delete keyword has limitations. It cannot delete keywords defined using var, let, or const

So it’s better to let the GC recycle naturally by dereferencing the assignment.





Memory metrics

Memory allocation was explained earlier in Memory Control and V8 Memory Allocation, with examples of how to check for memory usage and out of memory.

Out of memory

As you can see from the results of process.momoryUsage, the amount of memory in the heap is always less than the amount of resident memory in the process, which means that not all memory usage in Node is allocated by V8, and that memory that is not allocated by V8 is called out-of-heap memory



A memory leak

Node is very sensitive to memory leaks, and under V8 garbage collection, memory leaks are rarely seen in normal code writing. Memory leaks usually occur between May 1 and May 1.

In general, memory leaks can be caused by one of the following:

  • The cache
  • Queue consumption is not timely
  • Scope is not released

Beware of using memory as a cache

Caching plays an important role in applications and can save resources effectively. The access efficiency is higher than the I/O efficiency. Once a cache is hit, an I/O time can be saved.

But caching is not cheap in Node. Once an object is used as a cache, it means that it will be resident in the old generation. The more keys that are stored in the cache, the more long-lived objects there are, causing the GC to scan and collate, making no use of these objects.

Solution:

  1. Use cache limiting policies to limit the number of caches
  2. The cache is shared between processes
  3. Move the cache outside, reduce the number of objects residing in memory, and make garbage collection higher

Paying attention to queue status

After the memory leak problem caused by caching, queue consumption is the memory leak problem

Queues often act as intermediates in the consumer-producer model, but in most application scenarios, the rate of consumption is much faster than the rate of production, and memory leaks are less likely to occur. However, if the consumption rate exceeds the production rate, it is likely to accumulate.

Solution:

  1. Monitoring the length of the queue, once the accumulation, should be through the monitoring system to generate an alarm and inform the relevant personnel
  2. Any asynchronous invocation should include a timeout mechanism that, if no response is generated within a certain amount of time, passes the timeout exception through the callback function to put a lower limit on the consumption speed.


Large memory application

In Node, it is inevitable that large files will be manipulated, and large files should be handled with caution due to Node’s memory limitations.

However, Node provides stream modules for processing large files, which inherit from EventEmitter and provide basic custom event functionality while abstracted from standard events and methods.

Flow is one of the basic concepts that powers Node.js applications. They are a way to handle reading/writing files, network communication, or any type of end-to-end information exchange in an efficient manner. The Stream module of Node.js provides the basis on which to build all the stream apis. All streams are instances of EventEmitter

Fs.readfile () and fs.writefile () cannot be used directly for large file operations due to V8 memory limitations. Fs.createreadstream () and fs.createWritestream () are used to stream large files. With streams, you can read and process them fragment by fragment (without having to keep them all in memory)

Streams basically offer two major advantages over other data processing methods:

  • Memory efficiency: Storage without loading large amounts of data into memory
  • Time efficiency: It takes less time to start processing data as soon as it is acquired, rather than waiting for the entire data payload to become available. And you can control the speed of the flow

Example:

const fs = require('fs');
/ / create the stream
let reader = fs.createReadStream('isolate-v8.log');
let writer = fs.createWriteStream('new-v8.log');

function save() {
    reader.on('data'.(data) = >{
        writer.write(data);
    })
    reader.on('end'.function() { writer.end(); })}Copy the code