V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
zy445566
V2EX  ›  程序员

利用 require-vm 实现一个更安全的引用,更好的防止内存泄漏篇

  •  
  •   zy445566 ·
    zy445566 · 2020-07-03 09:28:58 +08:00 · 687 次点击
    这是一个创建于 1386 天前的主题,其中的信息可能已经有所发展或是发生改变。

    之前由于遇到了发现泄漏变量,但是代码结构庞大无法直接修改。就好像你引用了别人一个庞大的库,而且这个库有 10000 多个内存泄漏的方法等着你去修复,但这任务量无疑是庞大的。所以为了解决这个问题开发了require-vm库来彻底解决这个问题。

    内存泄漏类型测试

    现在引用一下 N 年前的老帖《 Node 内存泄漏专题》,里面几乎涵盖了内存泄漏的几种类型,那我们就根据这几种类型来验证require-vm是否能解决,这些内存泄漏问题。

    Case1:无限制增长的数组

    泄漏案例:

    // case1.js
    var leakArray = [];   
    exports.leak = function () {  
      leakArray.push("leak" + Math.random());  
    };
    

    如果按照原来的方式 requie,那么 leakArray 就不可能被回收,内存就会无限上涨连续运行 3 分钟内基本就爆出内存耗尽,如下:

    while(true) {
        require('./case1.js').leak()
    }
    

    但是如果你使用require-vm却可以无限运行,如下:

    const requireVM = require('require-vm');
    while(true) {
      requireVM('./case1.js').leak()
    }
    

    看来 case1,require-vm如果针对不小心使用了内存泄漏的库可以很方便的解决,而如果要你要保留 leakArray 数组的话,也只需要保证 leak 方法,不被释放即可,如下。

    const requireVM = require('require-vm');
    let leak = requireVM('./case1.js').leak;
    // 下面三个方法还保持 leakArray 数组还存活
    leak();
    leak();
    leak();
    // 但此时只要 leak 被重新赋值,即可释放 leakArray 空间
    leak = null;
    

    Case2:无限制设置属性和值

    泄漏案例:

    // case2.js
    _.memoize = function(func, hasher) {
      var memo = {};
      hasher || (hasher = _.identity);
      return function() {
        var key = hasher.apply(this, arguments);
        return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
      };
    };
    

    memoize 方法主要是为了用于缓存计算结果的,但是为了缓存计算结果会把计算值保留,但随着保留的多,数据会直接爆炸,导致内存耗尽。如下:

    function getN (n) {
      return n;
    };
    let memoize = require('lodash').memoize
    let memoizeGetN = memoize(getN);
    let i=0n;
    while(true) {
      memoizeGetN(i);
      i++;
    }
    

    像这种情况,因为 memo 对象被 memoizeGetN 引用,而 memoizeGetN 变量又没有被销毁,所以这个只能手动销毁,require-vm也需要你手动销毁引用,如下:

    const requireVM = require('require-vm');
    function getN (n) {
      return n;
    };
    let memoize = requireVM('lodash').memoize
    let memoizeGetN = memoize(getN);
    let i=0n;
    while(true) {
      memoizeGetN(i);
      /**
       * 当重新赋值后原 memo 的引用也会被丢弃
       * 但这样做原本用于缓存计算结果也无效了
       */
      memoizeGetN = memoize(getN);
      i++;
    }
    

    Case3:任何模块内的私有变量和方法均是永驻内存的

    泄漏案例:

    (function (exports, require, module, __filename, __dirname) {
        // case3.js ------------------------
        var circle = require('./circle.js');
        console.log('The area of a circle of radius 4 is ' + circle.area(4));
        exports.get = function () {
           return circle();
        };
        // case3.js ------------------------
    });
    

    其实这个东西和 Case1 很像,目前也确实是会存在问题,原因是 require 存在 cache 和其中的某些变量会存在全局,而且 require 来清除里面的变量并不是很方便,所以如下:

    let get = require('./case3.js').get;
    // 这里无论你怎么样处理,case3.js 里面的 circle 变量都不回被回收。
    get = null;
    

    如果使用require-vm解决,则把内存泄漏的可能交给自己手动解决,只要你修改引用关系即可被回收,如下:

    const requireVM = require('require-vm');
    let get = requireVM('./case3.js').get;
    /**
     * 如果使用 requireVM,则 get 值被重置引用关系直接会被解除
     * 所以 case3.js 里面的 circle 变量走完 get = null 就直接被回收
     */
    get = null;
    

    同时如果存在全局变量的情况下,requireVM 依旧可以回收,假设 case3.js 里面的 circle 变量是全局变量,则使用这种方式手动控制回收,如下:

    const requireVM = require('require-vm');
    let context = {}
    let get = requireVM('./case3.js',context).get;
    /**
     * 因为我们假设 circle 变量是全局变量
     * 传入上下文,使变量绑定在上下文中
     * 重新赋值使变量随着原来的上下文一起回收
     */
    context = {};
    

    Case4: 大循环,无 GC 机会

    泄漏案例:

    //OOM 测试
    for ( var i = 0; i < 100000000; i++ ) {
        var user       = {};
        user.name  = 'outmem';
        user.pass  = '123456';
        user.email = 'outmem[@outmem](/user/outmem).com';
    }
    

    这种现在已经没有任何意义,现在即使在循环内,V8 也会进行内存回收。

    requireVM 的优势

    requireVM 的目标是实现一个更安全,更可控的 require 引用,目前来看确实是做到了,它可以仅可能阻断内存泄漏,即使引用的包不能被修改,也可以实现手动实现引用包的内存释放。同时可定义 moduleMap 来实现引用替换,比如实现改写 fs 模块返回,从此引用第三方包不再需要提心吊胆。所以即使第三方库有 10000 个泄漏点,也无需修改第三方库代码而实现内存释放。欧耶!

    目前尚无回复
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   5270 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 05:58 · PVG 13:58 · LAX 22:58 · JFK 01:58
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.