ホームページ > 記事 > ウェブフロントエンド > V8 のメモリ管理とガベージ コレクション アルゴリズムについて話しましょう
この記事では、V8 エンジンのメモリ管理とガベージ コレクション アルゴリズムについて説明します。お役に立てば幸いです。
ご存知のとおり、JS はガベージ コレクションを自動的に管理するため、開発者はメモリの割り当てやリサイクルについて気にする必要はありません。さらに、ガベージ コレクション メカニズムも、フロントエンドのインタビューで一般的にテストされる部分です。この記事では主に V8 の世代別ガベージ コレクション アルゴリズムについて説明します。この記事を読んだ友人が V8
ガベージ コレクションの仕組みについて 痛い
を感じてくれれば幸いです (笑、## です) #painful!!!)、この記事では主に次の内容について説明します:
メモリの制限と解決策
アルゴリズム
存続オブジェクトをマークするためのロジックおよび最適化方法
アルゴリズムの深さ/幅優先の違い
STW原因と最適化戦略
process.memoryUsage(); // 返回内存的使用量,单位字节 { rss: 22953984, // 申请的总的堆内存 heapTotal: 9682944, // 已使用的堆内存 heapUsed: 5290344, external: 9388 }
V8メモリを制限する別の方法があります。メモリ使用量のサイズ 重要な理由は、ヒープ メモリが大きすぎると、
V8 がガベージ コレクションの実行に長い時間がかかることです (
1.5g には
50ms かかります)。非増分ガベージ コレクションの実行には時間がかかります (
1.5g から
1s)。
V8 のガベージコレクションの仕組みについては後ほど説明すると、皆さんもより共感できると思います。
V8 エンジンにはメモリ使用量の制限がありますが、メモリ制限を変更する方法も公開されています。これは、
V8 エンジンの起動時に関連パラメータを追加することです。次のコードは、
Node の依存する
V8 エンジン メモリ制限を変更するで説明されています:
# 更改老生代的内存限制,单位mb node --max-old-space-size=2048 index.js # 更改新生代的内存限制,单位mb node --max-semi-space-size=1024=64 index.jsここで、変更された新世代メモリの構文に注意してください。が上記の書き方に変更され、単位も
kb から
mb に変更されました。以前の書き方は
node --max-new-space- です。 size。次のコマンドを使用して現在の ## をクエリできます。#Node
Environment を使用して、新しい世代のメモリの構文を変更します: <pre class="brush:js;toolbar:false;">node --v8-options | grep max</pre>
生存時間 に基づいてメモリ ガベージを世代に分割します。 世代別ガベージ コレクション アルゴリズム は、メモリ ガベージの種類ごとに異なるメソッドを実装します。リサイクル アルゴリズム。 V8
メモリを 2 つのタイプに分割します:新しい世代 と
古い世代 :
新しい世代では世代メモリ 旧世代メモリ内のオブジェクトの生存期間は短くなります。
oldspace) に格納されます。
##新入生世代のメモリは Scavenge
アルゴリズムを使用しますMark-スイープ
を使用し、
Scavengeスカベンジ アルゴリズム
アルゴリズムです。 Cheney
アルゴリズムは、新世代のメモリ空間を 2 つに分割し、1 つの空間は使用中 (FromSpace
)、もう 1 つの空間はアイドル状態 (ToSpace# と呼ばれます) にします。 ##) 。
<p>在内存开始分配时,首先在<code>FromSpace
中进行分配,垃圾回收机制执行时会检查FromSpace
中的存活对象,存活对象会被会被复制到ToSpace
,非存活对象所占用的空间将被释放,复制完成后FromSpace
和ToSpace
的角色将翻转。当一个对象多次复制后依然处于存活状态,则认为其是长期存活对象,此时将发生晋升,然后该对象被移动到老生代空间oldSpace
中,采用新的算法进行管理。
Scavenge
算法其实就是在两个空间内来回复制存活对象,是典型的空间换时间做法,所以非常适合新生代内存,因为仅复制存活的对象且新生代内存中存活对象是占少数的。但是有如下几个重要问题需要考虑:
假设存在三个对象temp1、temp2、temp3
,其中temp2、temp3
都引用了temp1
,js代码示例如下:
var temp2 = { ref: temp1, } var temp3 = { ref: temp1, } var temp1 = {}
从FromSpace
中拷贝temp2
到ToSpace
中时,发现引用了temp1
,便把temp1
也拷贝到ToSpace
,是一个递归的过程。但是在拷贝temp3
时发现也引用了temp1
,此时再把temp1
拷贝过去则重复了。
要避免重复拷贝,做法是拷贝时给对象添加一个标记visited
表示该节点已被访问过,后续通过visited
属性判断是否拷贝对象。
还是上述引用关系,由于temp1
不需要重复拷贝,temp3
被拷贝到ToSpace
之后不知道temp1
对象在ToSpace
中的内存地址。
做法是temp1
被拷贝过去后该对象节点上会生成新的field
属性指向新的内存空间地址,同时更新到旧内存对象的forwarding
属性上,因此temp3
就可以通过旧temp1
的forwarding
属性找到在ToSpace
中的引用地址了。
内存对象同时存在于新生代和老生代之后,也带来了问题:
const temp1 = {} const temp2 = { ref: temp1, }
比如上述代码中的两个对象temp1
和temp2
都存在于新生代,其中temp2
引用了temp1
。假设在经过GC
之后temp2
晋升到了老生代,那么在下次GC
的标记阶段,如何判断temp1
是否是存活对象呢?
在基于可达性分析算法中要知道temp1
是否存活,就必须要知道是否有根对象引用
引用了temp1
对象。如此的话,年轻代的GC
就要遍历所有的老生代对象判断是否有根引用对象引用了temp1
对象,如此的话分代算法就没有意义了。
解决版本就是维护一个记录所有的跨代引用的记录集,它是写缓冲区
的一个列表。只要有老生代中的内存对象指向了新生代内存对象时,就将老生代中该对象的内存引用记录到记录集中。由于这种情况一般发生在对象写的操作,顾称此为写屏障,还一种可能的情况就是发生在晋升时。记录集的维护只要关心对象的写操作和晋升操作即可。此是又带来了另一个问题:
优化的手段是在一些Crankshaft
操作中是不需要写屏障的,还有就是栈上内存对象的写操作是不需要写屏障的。还有一些,更多的手段就不在这里过多讨论。
Scavenge
算法内存利用率不高问题新生代内存中存活对象占比是相对较小的,因此可以在分配空间时,ToSpace
可以分配的小一些。做法是将ToSpace
空间分成S0
和S1
两部分,S0
用作于ToSpace
,S1
与原FromSpace
合并当成FromSpace
。
垃圾回收算法中,识别内存对象是否是垃圾的机制一般有两种:引用计数和基于可达性分析。
到達可能性分析に基づいて、すべての ルート参照 (グローバル変数など) を検索し、すべてのルート参照、再帰的ルート参照上のすべての参照を走査します。走査されるものはすべて ライブ オブジェクト を指定してマークします。この時点では、空間内の他のメモリ オブジェクトは デッド オブジェクトであるため、有向グラフが構築されます。
再帰の制限を考慮して、再帰ロジックは通常、非再帰実装を採用します。一般的なものには、幅優先アルゴリズムと深さ優先アルゴリズムが含まれます。 2 つの違いは次のとおりです。
ToSpace
にコピーするときにメモリ オブジェクトの順序を変更し、参照関係を持つオブジェクトを近づけます。その理由は、自身をコピーした後、自身が参照するオブジェクトが直接コピーされるため、関連するオブジェクトが ToSpace
CPU のキャッシュ戦略により、メモリ オブジェクトを読み取るとき、より速くキャッシュにアクセスするために、その背後にあるオブジェクトが一緒に読み取られる可能性が高くなります。コード開発中の非常に一般的なシナリオは obj1.obj2.obj3
であるため、このとき、CPU が obj1
を読み取ると、次の obj2
であれば、 obj3
も一緒に読み込むとキャッシュにヒットするので非常に助かります。
したがって、深さ優先アルゴリズムは、ビジネス ロジックがキャッシュにヒットするのに役立ちますが、その実装には、メモリ領域を消費する追加のスタック支援アルゴリズムの実装が必要です。逆に、幅優先ではキャッシュ ヒットを改善することはできませんが、その実装ではポインタを使用してスペースの消費を巧みに回避でき、アルゴリズムの実行効率が高くなります。
新世代のメモリ オブジェクトを旧世代に昇格させる場合は、次の条件を満たす必要があります。
Scavenge
RecyclingToSpace
メモリ使用率が制限を超えることはできませんScavenge の GC のロジックは、
GC のたびに生き残ったオブジェクトの age
属性を与えることです 1
、そして
GC## が再度実行されたとき # のときの age
属性を判断するだけです。基本的なプロモーション図は次のとおりです。
旧世代のメモリには長期にわたって存続するオブジェクトが多数あり、
Scavenge アルゴリズムが使用される理由リサイクルできないのは次のとおりです:
(Mark-スイープ
) および Mark-スイープ## を使用します。 #(
Mark -Compact) の組み合わせ方法。マーキングとクリアは 2 つの部分に分かれています。
#マーキング フェーズ
上図に示すように、マークのクリアの問題点の 1 つは、クリア後に使用できなくなる不連続な領域が存在するため、旧世代のメモリ クリーンアップが必要になることです。メモリ容量が必要なため、タグ付けと組み合わせたソリューション。この解決策は、マーキング プロセス中に生物オブジェクトを片側に移動し、移動の完了後に境界の外側にあるすべての非生物オブジェクトをクリーンアップして削除することです。
#ガベージ コレクションの完全な一時停止
と呼ばれ、STW と呼ばれます。若い世代のメモリのガベージ コレクションはアプリケーションの実行にほとんど影響を与えませんが、旧世代のメモリには多くのオブジェクトが生き残っているため、旧世代のメモリのガベージ コレクションによって引き起こされる完全な一時停止の影響は非常に大きくなります。
incremental mark、
concurrency mark、 も導入しています。並行マーク
、増分クリーニング
、並行クリーニング
、遅延クリーニング
などの方法。 STW 最適化
GC プロセスは多くのことを実行する必要があり、メイン スレッドで STW 現象が発生します。並列 GC の方法は、複数の補助スレッドを開くことです。 GC 作業を共有するスレッド。このアプローチでも STW 現象を回避することはできませんが、有効な補助スレッドの数に応じて STW の合計時間を短縮できます。
でのアニメーションのレンダリングはおよそです。 ##60 フレーム (各フレームは約
16ms)、現在のレンダリング時間が
16.6ms に達すると、次のような他のことを行うための自由時間が生まれます。パート番号 ##GC
タスク。
Node
に保存するためにメモリを使用しないでください。そうしないと、大量のユーザー セッション オブジェクトをメモリに保存すると、古い世代のメモリが急増します。 、クリーンアップのパフォーマンスに影響を及ぼし、アプリケーションの実行パフォーマンスとメモリ オーバーフローに影響を与えます。 Redis の使用方法の改善など。キャッシュを外部に移動する利点:キャッシュをプロセス間で共有できる
以上がV8 のメモリ管理とガベージ コレクション アルゴリズムについて話しましょうの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。