Vueのプロジェクトメモリリーク分析とブラウザのガベージコレクション

入門

自動ガベージコレクション(GC:ガベージCollecation)でお使いのブラウザでJavaScript、つまり、プロシージャのコードの実行環境は、メモリの使用を管理する責任を負います。原理は、定期的(周期的)ガベージコレクタは、これらの変数を使用し続けることはありません特定し、そのメモリを解放しますしかし、この方法は、リアルタイムではなく、ガベージコレクタは、定期的に定期的な間隔で実行するように、その比較的大きなオーバーヘッドGCの、他のに応答して動作を停止します。

変数のライフサイクルの終わりがもはや使用されていない変数は、当然のことながら、唯一のブラウザページが終了しますアンロードされるまで、ローカル変数、グローバル変数のライフサイクルことができます。ローカル変数は、関数の実行中にのみ存在し、その過程で機能が完了するまで、その後、それらの値を格納する機能でこれらの変数を使用するそれぞれの空間ヒープまたはスタック上に割り当てられたローカル変数になり、閉鎖します内部関数、外部関数によるパッケージとは、エンド考えることはできません。

またはコードがそれを示しています。

関数もう一度Fn1(){ 
 VAR OBJ = {名: 'hanzichi'、年齢:10}; 
} 
関数Fn2が(){ 
 VAR OBJ = {名: 'hanzichi'、年齢:10}; 
 OBJを返す; 
} 
VARのA =もう一度Fn1( ); 
VAR Fn2がB =(); 
重複コード

私たちは、コードが実行される方法を見て。環境へのFN1、FN1を呼び出したときに最初の文の二つの機能、およびFn2がは、格納されたメモリオブジェクトを開きます、FN1と呼ばれている{名:「hanzichi」、年齢:10}、およびコールが終了、呼ばれる手順でFN2は、オブジェクトがグローバル変数B点に戻されるので、メモリブロックは解放されません; FN1環境は、その後、メモリブロックは自動的にJSエンジンガベージコレクタが解除されます。

ここで疑問が生じる:役に立たない変数最後に?だから、ガベージコレクタは、最終的には無用どの変数を追跡する必要があり、将来的にはそのメモリを回復するための変数は、マークをマークするために、もはや有益ではありません。:変数の無益マークのための戦略により実現し、そこに通常2つの方法で実装に異なる場合がクリアマーキング参照カウントをあまり一般的では参照カウント、明確なより一般的に使用されるマーカー。

2.マーク・クリア

JSガベージコレクションの中で最も一般的に使用されるフォームがクリアマークされます。変数は環境中に、例えば、変数の関数宣言では、変数は、「環境に。」としてマークされます場合には 論理的には、それは決して占有し、環境変数のメモリ限り、対応する環境への実行の流れの中に放出されていない、あなたはそれらを使用する場合があります。環境変数を残す場合は、「環境を残す。」とマークされています

試験関数(){ 
; //マークされ、環境に10 = VARのA  
; VAR B = 20は、//環境に、マーク
} 
テスト(); //後に終了し、Bは環境を残すためにマークされ、回収されました。
コードをコピー

時間を実行しているすべての変数のガベージコレクタは、(もちろん、あなたが任意のマークされた方法を使用することができます)とマークされているメモリに保存されます。その後、それは変数の環境変数(閉鎖)参照タグと環境変数を削除します。その変数がマークされた後、環境変数は、これらの変数にアクセスすることができなかったので、その後、変数を削除する準備が考慮されます。最後に、ガベージコレクタのメモリは、クリーンアップ、タグ付けされたと彼らが占めるメモリ空間の回復、これらの値の破壊を完了します。これまでのところ、IE9 +、Firefoxの、オペラ、Chromeは、SafariのJS実装は、互いに異なる間隔ガーベジコレクションのポリシーの削除または類似の戦略が、ガベージコレクションの時間を使用兆候です。

3.参照カウント

基準カウント値のそれぞれを回数を意味して実績を参照されます。値は変数の型に割り当てられたときに、可変基準引用を宣言する場合、この値は1です。値は同じ参照番号が別の変数に割り当てられている場合、値がインクリメントされます。値は、基準変数が別の値になった場合に備えて、基準は逆に、この値を1だけデクリメントされます。この数は、0の基準値は、この値にアクセスする方法はありませんとなり、バック占有メモリ空間に再循環させることができるとき。次回は、ガベージコレクタの実行時にこのように、それは0のメモリ参照番号値が占有することを解放します。

試験関数(){ 
 VARのA = {}; //オブジェクト1への参照の数
 のvar A = B; //オブジェクトの数プラス1への言及は、2である
 VaRのC = A; //指示物体参照番号プラス1と3。
 varは= {} B; //オブジェクトの数マイナス1を参照し、2 
} 
のコードをコピー

NetscapeのNavigator3参照カウント戦略ブラウザを使用する最初の、それはすぐに深刻な問題が発生しました:循環参照を円形の参照オブジェクトAがオブジェクトBを指すポインタを含むことをいう、オブジェクトBは、基準オブジェクトへのポインタを含みます。

FN関数(){ 
 VARのA = {}; 
 VARのB = {}; 
 a.pro = B; 
 b.pro = A; 
} 
FN()は、
コードの重複しました

引用A及びBの上記のコード番号FN終了、2であり、2つのオブジェクトがマーク明確な方法の下で、環境を残しているとbが引用されないので、問題ありませんが、参照カウント戦略の0、それは多数のFN関数が呼び出された場合、それはメモリリークが発生します、ゴミ収集メモリではありません。IE7とIE8で、メモリまっすぐ。

私たちが知っている、IEは、ネイティブJSオブジェクトがあるオブジェクトの一部ではありません。例えば、DOMおよびBOMオブジェクト内のメモリリークがCOMオブジェクトの形でC ++実装を使用することであり、ガベージコレクション機構は、参照カウント戦略を使用してCOMオブジェクトです。そのため、IEのエンジンは、JSが達成するための明確な戦略をマーク使用している場合でも、それにJSアクセスCOMオブジェクトがまだ参照カウント戦略に基づいているの。換言すれば、限りIEのCOMオブジェクトとしては、循環参照を提示します。

VAR要素=のdocument.getElementById( "some_element")。
するvar myObjectという=新しいオブジェクト(); 
myObject.e =エレメント。
element.o = myObjectという。
复制代码

ネイティブJSオブジェクトとDOM要素要素間のこの例では、循環参照を作成myObjectという。eは要素オブジェクトを指し示すプロパティ変数myObjectというを有し;そして可変O素子は、プロパティ照応myObjectというを有しています。このため、循環参照のため、場合は、ページのDOMから削除された場合でも、それが回復することはありません。

Vueのプロジェクトメモリリーク分析とブラウザのガベージコレクション


  • オレンジは、メモリ内の直接参照JS変数を参照します

  • 上記のように赤手段のJSは、間接的に、変数を参照し、refBのrefAともrefBのが空になった引き起こす可能性が間接参照変数、それが回収されることはありませんでした

  • 子要素は、間接参照のparentNodeをREFBとして、限り、それが削除されないよう、その親要素(図の赤い部分)の全てが削除されます

別の例:

window.onload =関数outerFunction(){ 
 VAR OBJ =のdocument.getElementById( "要素")。
 obj.onclick =関数innerFunction(){}。
}; 
复制代码

このコードは大丈夫に見えますが、引用されたのdocument.getElementById(「要素を」)objを、のdocument.getElementById(「要素」)のonclickメソッドが自然OBJを含む外部環境変数で参照されている間、それは非常に微妙ではありませんああ。(新しいブラウザではすでに上でノードを削除する場合は、イベントを削除しますが、古いブラウザでは、特に、IEでこのバグを持っています)

ソリューション:

最も簡単な方法は、そのようなだけで機能することができ、このように、自分の手循環参照を持ち上げることです

myObject.element = null;
element.o = null;
window.onload=function outerFunction(){
 var obj = document.getElementById("element");
 obj.onclick=function innerFunction(){};
 obj=null;
};
复制代码

将变量设置为 null 意味着切断变量与它此前引用的值之间的连接。当垃圾回收器下次运行时,就会删除这些值并回收它们占用的内存。

要注意的是,IE9+ 并不存在循环引用导致 DOM 内存泄露问题,可能是微软做了优化,或者 DOM 的回收方式已经改变。

4. 内存管理

4.1 什么时候触发垃圾回收?

垃圾回收器周期性运行,如果分配的内存非常多,那么回收工作也会很艰巨,确定垃圾回收时间间隔就变成了一个值得思考的问题。IE6的垃圾回收是根据内存分配量运行的,当环境中存在256个变量、4096个对象、64k 的字符串任意一种情况的时候就会触发垃圾回收器工作,看起来很科学,不用按一段时间就调用一次,有时候会没必要,这样按需调用不是很好吗?但是如果环境中就是有这么多变量等一直存在,现在脚本如此复杂,很正常,那么结果就是垃圾回收器一直在工作,这样浏览器就没法儿玩儿了。

微软在IE7中做了调整,触发条件不再是固定的,而是动态修改的,初始值和 IE6 相同,如果垃圾回收器回收的内存分配量低于程序占用内存的15%,说明大部分内存不可被回收,设的垃圾回收触发条件过于敏感,这时候把临街条件翻倍,如果回收的内存高于85%,说明大部分内存早就该清理了,这时候把触发条件置回。这样就使垃圾回收工作职能了很多

4.2 合理的GC方案

1. 基础方案

Javascript引擎基础GC方案是(simple GC):mark and sweep(标记清除),即:

  1. 遍历所有可访问的对象。

  2. 回收已不可访问的对象。

2. GC的缺陷

和其他语言一样,JS 的 GC 策略也无法避免一个问题:GC 时,停止响应其他操作,这是为了安全考虑。而 Javascript 的 GC 在 100ms 甚至以上,对一般的应用还好,但对于 JS 游戏,动画对连贯性要求比较高的应用,就麻烦了。这就是新引擎需要优化的点:避免GC造成的长时间停止响应。

3. GC优化策略

David 大叔主要介绍了2个优化方案,而这也是最主要的2个优化方案了:

  1. 分代回收(Generation GC) 这个和Java回收策略思想是一致的,也是V8所主要采用的。目的是通过区分“临时”与“持久”对象;多回收“临时对象”区(young generation),少回收“持久对象”区(tenured generation),减少每次需遍历的对象,从而减少每次GC的耗时。如图:

Vueのプロジェクトメモリリーク分析とブラウザのガベージコレクション


  1. 这里需要补充的是:对于 tenured generation 对象,有额外的开销:把它从 young generation 迁移到 tenured generation,另外,如果被引用了,那引用的指向也需要修改。 这里主要内容可以参考深入浅出Node中关于内存的介绍,很详细~

  2. 增量GC 这个方案的思想很简单,就是“每次处理一点,下次再处理一点,如此类推”。如图:

Vueのプロジェクトメモリリーク分析とブラウザのガベージコレクション


这种方案,虽然耗时短,但中断较多,带来了上下文切换频繁的问题。因为每种方案都其适用场景和缺点,因此在实际应用中,会根据实际情况选择方案。

比如:低 (对象/s) 比率时,中断执行GC的频率,simple GC更低些;如果大量对象都是长期“存活”,则分代处理优势也不大。

5. Vue 中的内存泄漏问题

JS 程序的内存溢出后,会使某一段函数体永远失效(取决于当时的 JS 代码运行到哪一个函数),通常表现为程序突然卡死或程序出现异常。

这时我们就要对该 JS 程序进行内存泄漏的排查,找出哪些对象所占用的内存没有释放。这些对象通常都是开发者以为释放掉了,但事实上仍被某个闭包引用着,或者放在某个数组里面。

5.1 泄漏点

  1. DOM/BOM 对象泄漏;

  2. script 中存在对 DOM/BOM 对象的引用导致;

  3. JS 对象泄漏;

  4. 通常由闭包导致,比如事件处理回调,导致 DOM 对象和脚本中对象双向引用,这个是常见的泄漏原因;

5.2 代码关注点

主要关注的就是各种事件绑定场景,比如:

  1. DOM 中的 addEventLisner 函数及派生的事件监听,比如 Jquery 中的 on 函数,Vue 组件实例的 $on 函数;

  2. 其它 BOM 对象的事件监听, 比如 websocket 实例的 on 函数;

  3. 避免不必要的函数引用;

  4. 如果使用 render 函数,避免在 HTML 标签中绑定 DOM/BOM 事件;

5.3 如何处理

  1. 如果在 mounted/created 钩子中使用 JS 绑定了 DOM/BOM 对象中的事件,需要在 beforeDestroy 中做对应解绑处理;

  2. 如果在 mounted/created 钩子中使用了第三方库初始化,需要在 beforeDestroy 中做对应销毁处理(一般用不到,因为很多时候都是直接全局 Vue.use);

  3. 如果组件中使用了 setInterval,需要在 beforeDestroy 中做对应销毁处理;

5.4 在 vue 组件中处理 addEventListener

调用 addEventListener 添加事件监听后在 beforeDestroy 中调用 removeEventListener 移除对应的事件监听。为了准确移除监听,尽量不要使用匿名函数或者已有的函数的绑定来直接作为事件监听函数。

mounted() {
 const box = document.getElementById('time-line')
 this.width = box.offsetWidth
 this.resizefun = () => {
 this.width = box.offsetWidth
 }
 window.addEventListener('resize', this.resizefun)
 },
 beforeDestroy() {
 window.removeEventListener('resize', this.resizefun)
 this.resizefun = null
 }
复制代码

5.5 观察者模式引起的内存泄漏

在 spa 应用中使用观察者模式的时候如果给观察者注册了被观察的方法,而没有在离开组件的时候及时移除,可能造成重复注册而内存泄漏;

举个栗子: 进入组件的时候 ob.addListener("enter", _func),如果离开组件 beforeDestroy 的时候没有 ob.removeListener("enter", _func),就会导致内存泄漏

5.6 上下文绑定引起的内存泄漏

有时候使用 bind/apply/call 上下文绑定方法的时候,会有内存泄漏的隐患。

var ClassA = function(name) {
 this.name = name
 this.func = null
}
var a = new ClassA("a")
var b = new ClassA("b")
b.func = bind(function() {
 console.log("I am " + this.name)
}, a)
b.func() // 输出: I am a
a = null // 释放a
//b = null; // 释放b
//b.func = null; // 释放b.func
function bind(func, self) { // 模拟上下文绑定
 return function() {
 return func.apply(self)
 }
}
复制代码

使用 chrome dev tool > memory > profiles 查看内存中 ClassA 的实例数,发现有两个实例,a 和 b。虽然 a 设置成 null 了,但是 b 的方法中 bind 的闭包上下文 self 绑定了 a,因此虽然 a 释放,但是 b/b.func 没有释放,闭包的 self 一直存在并保持对 a 的引用。


网上的帖子大多深浅不一,甚至有些前后矛盾,在下的文章都是学习过程中的总结,如果发现错误,欢迎留言指出~

Vueのプロジェクトメモリリーク分析とブラウザのガベージコレクション


おすすめ

転載: blog.51cto.com/14516511/2438243