JavaScriptのメモリ管理を詳しく解説
関連する推奨事項: 「JavaScript ビデオ チュートリアル 」
ほとんどの場合、私たちはメモリ管理に関する知識を理解せずに開発するだけです。 JS エンジンがこれを処理します。ただし、メモリ リークなどの問題が発生する場合があり、メモリ割り当ての仕組みを知ることによってのみ、これらの問題を解決できます。
この記事では、主に メモリ割り当て と ガベージ コレクション の動作原理と、いくつかの一般的な メモリ リーク 問題を回避する方法を紹介します。
キャッシュ (メモリ) ライフ サイクル
JS では、変数、関数、またはオブジェクトを作成すると、JS エンジンがそれにメモリを割り当て、不要になったら解放します。
メモリの割り当てはメモリ内のスペースを予約するプロセスであり、メモリの解放はスペースを解放して他の目的に備えます。
変数を割り当てたり、関数を作成したりするたびに、その変数の保存は同じ段階を経ます:
メモリの割り当て
- JS がこれを処理し、オブジェクトの作成に必要なメモリを割り当てます。
メモリの使用
- メモリの使用は、コード内で明示的に実行します: メモリの読み取りと書き込みは、実際には変数の読み取りと書き込みです。書く。
メモリの解放
- このステップも JS エンジンによって処理され、割り当てられたメモリが解放されると、新しい目的に使用できるようになります。 。
メモリ管理の文脈における「オブジェクト」には、JS オブジェクトだけでなく、関数や関数スコープも含まれます。
メモリ ヒープとスタック
これで、JS で定義したすべてのものに対して、エンジンがメモリを割り当て、メモリが不要になったときにメモリを解放することがわかりました。
次に頭に浮かぶ疑問は、これらのものはどこに保管されるのかということです。
JS エンジンは、メモリ ヒープと スタックの 2 つの場所にデータを保存できます。ヒープとスタックは、エンジンによってさまざまな目的で使用される 2 つのデータ構造です。
スタック: 静的メモリ割り当て
スタックは、JS が静的データを保存するために使用するデータ構造です。静的データは、エンジンがコンパイル時にサイズを認識しているデータです。 JS には、オブジェクトと関数を指すプリミティブ値が含まれます (strings
、number
、boolean
、undependent
、および null
) と参照型。
エンジンはサイズが変わらないことを認識しているため、各値に固定量のメモリを割り当てます。
実行直前にメモリを割り当てるプロセスは、静的メモリ割り当てと呼ばれます。これらの値とスタック全体の制限はブラウザーに依存します。
ヒープ: 動的メモリ割り当て
ヒープはデータを保存するための別のスペースであり、JS はここに objects と functions を保存します。
スタックとは異なり、JS エンジンはこれらのオブジェクトに固定量のメモリを割り当てませんが、必要に応じてスペースを割り当てます。このメモリ割り当て方法は、動的メモリ割り当てとも呼ばれます。
これら 2 つのストアの特徴を以下で比較します。
スタック | ヒープ |
---|---|
ストレージの基本型と参照 サイズはコンパイル時に判明します 固定量のメモリを割り当てます |
オブジェクトと関数 サイズはコンパイル時に判明しますランタイム 制限なし |
例
イメージを強化するためにいくつかの例を見てみましょう。
const person = { name: 'John', age: 24, };
JS は、ヒープ内のこのオブジェクトにメモリを割り当てます。実際の値は元の値のままであるため、スタックに保存されます。
const hobbies = ['hiking', 'reading'];
配列もオブジェクトなので、ヒープに格納されます。
let name = 'John'; // 为字符串分配内存 const age = 24; // 为字分配内存 name = 'John Doe'; // 为新字符串分配内存 const firstName = name.slice(0,4); // 为新字符串分配内存
初期値は不変であるため、JS は元の値を変更せず、新しい値を作成します。
JavaScript での参照
すべての変数は最初に stack を指します。非プリミティブ値の場合、 スタック
には ヒープ
内のオブジェクトへの参照が含まれます。
ヒープ メモリは特定の方法で並べ替えられていないため、スタック上にヒープ メモリへの参照を保持する必要があります。 references
をアドレス、ヒープ内のオブジェクトをそれらのアドレスが属するハウスと考えることができます。
JS は objects と functions をヒープに保存することに注意してください。プリミティブ型と参照はスタックに保存されます。
この写真では、さまざまな値がどのように保存されているかを観察できます。 person
と newperson
がどちらも同じオブジェクトを指していることに注目してください。
例
const person = { name: 'John', age: 24, };
これにより、ヒープ内に新しいオブジェクトが作成され、スタック上にそのオブジェクトへの参照が作成されます。
ガベージ コレクション
JS がさまざまなオブジェクトにメモリを割り当てる方法がわかりましたが、メモリのライフ サイクルには最後のステップが 1 つあります: メモリを解放する。
メモリ割り当てと同様に、JavaScript エンジンがこのステップも処理します。より具体的には、ガベージ コレクターがこのジョブを担当します。
JS エンジンは、変数または関数が不要になったことを認識すると、占有していたメモリを解放します。
これに関する主な問題は、一部のメモリがまだ必要かどうかが判断できないことです。つまり、不要になった瞬間にすべてのメモリを即座に収集できるアルゴリズムを使用することは不可能です。不要になりました。
一部のアルゴリズムはこの問題をうまく解決できます。このセクションでは、最も一般的な方法である 参照カウント
アルゴリズムと マーク クリアリング
アルゴリズムについて説明します。
参照カウント
変数が宣言され、その変数に参照型の値が割り当てられると、この値への参照の数は 1
になります。同じ値が別の変数に割り当てられている場合、その値への参照の数は 1
だけ増加します。逆に、この値への参照を含む変数が別の値を取得すると、この値への参照の数は 1
だけ減ります。
この値への参照の数が 0
になると、この値にアクセスする方法がなくなったことを意味するため、この値が占有しているメモリ領域を再利用できます。こうすることで、次回ガベージ コレクターが実行されるときに、参照がゼロの値によって占められていたメモリが解放されます。
以下の例を見てみましょう。
最後の参照がオブジェクトであるため、最後のフレームでは hobbies
だけがヒープに残ることに注意してください。
サイクル数
参照カウント
このアルゴリズムの問題は、循環参照が考慮されていないことです。これは、1 つ以上のオブジェクトが相互に参照しているが、コードからアクセスできなくなった場合に発生します。
let son = { name: 'John', }; let dad = { name: 'Johnson', } son.dad = dad; dad.son = son; son = null; dad = null;
親オブジェクトは相互に参照しているため、アルゴリズムは割り当てられたメモリを解放せず、両方のオブジェクトにアクセスできなくなります。
これらを null
に設定すると、それらはすべて受信参照を持っているため、参照カウント アルゴリズムはそれらが使用されなくなったことを認識しません。
マーク アンド クリア
マーク アンド クリア アルゴリズムには、循環依存関係に対するソリューションが含まれています。指定されたオブジェクトへの参照を単に計算するのではなく、root
オブジェクトからアクセスできるかどうかを検出します。
ブラウザの root
は window
オブジェクトですが、NodeJS の root
は global
です。
このアルゴリズムは、到達不能なオブジェクトをガベージとしてマークし、スキャン (収集) します。ルート オブジェクトは決して収集されません。
これにより、循環依存関係は問題なくなります。前の例では、dad
オブジェクトも son
オブジェクトもルートからアクセスできません。したがって、それらはすべてガベージとしてマークされ、収集されます。
このアルゴリズムは、2012 年以降、すべての最新のブラウザーに実装されています。パフォーマンスと実装のみが改善されていますが、アルゴリズムの核となる考え方は同じままです。
トレードオフ
自動ガベージ コレクションにより、メモリ管理に時間を無駄にすることなく、アプリケーションの構築に集中できるようになります。ただし、トレードオフもあります。
メモリ使用量
アルゴリズムはメモリがいつ必要でなくなるかを正確に認識できないため、JS アプリケーションは実際に必要なメモリよりも多くのメモリを使用する可能性があります。
オブジェクトがガベージとしてマークされている場合でも、割り当てられたメモリをいつ収集するかどうかはガベージ コレクタによって決定されます。
如果你希望应用程序尽可能提高内存效率,那么最好使用低级语言。 但是请记住,这需要权衡取舍。
性能
收集垃圾的算法通常会定期运行以清理未使用的对象。
问题是我们开发人员不知道何时会回收。 收集大量垃圾或频繁收集垃圾可能会影响性能。然而,用户或开发人员通常不会注意到这种影响。
内存泄漏
在全局变量中存储数据,最常见内存问题可能是内存泄漏。
在浏览器的 JS 中,如果省略var
,const
或let
,则变量会被加到window
对象中。
users = getUsers();
在严格模式下可以避免这种情况。
除了意外地将变量添加到根目录之外,在许多情况下,我们需要这样来使用全局变量,但是一旦不需要时,要记得手动的把它释放了。
释放它很简单,把 null
给它就行了。
window.users = null;
被遗忘的计时器和回调
忘记计时器和回调可以使我们的应用程序的内存使用量增加。 特别是在单页应用程序(SPA)中,在动态添加事件侦听器和回调时必须小心。
被遗忘的计时器
const object = {}; const intervalId = setInterval(function() { // 这里使用的所有东西都无法收集直到清除`setInterval` doSomething(object); }, 2000);
上面的代码每2秒运行一次该函数。 如果我们的项目中有这样的代码,很有可能不需要一直运行它。
只要setInterval
没有被取消,则其中的引用对象就不会被垃圾回收。
确保在不再需要时清除它。
clearInterval(intervalId);
被遗忘的回调
假设我们向按钮添加了onclick
侦听器,之后该按钮将被删除。旧的浏览器无法收集侦听器,但是如今,这不再是问题。
不过,当我们不再需要事件侦听器时,删除它们仍然是一个好的做法。
const element = document.getElementById('button'); const onClick = () => alert('hi'); element.addEventListener('click', onClick); element.removeEventListener('click', onClick); element.parentNode.removeChild(element);
脱离DOM引用
内存泄漏与前面的内存泄漏类似:它发生在用 JS 存储DOM
元素时。
const elements = []; const element = document.getElementById('button'); elements.push(element); function removeAllElements() { elements.forEach((item) => { document.body.removeChild(document.getElementById(item.id)) }); }
删除这些元素时,我们还需要确保也从数组中删除该元素。否则,将无法收集这些DOM元素。
const elements = []; const element = document.getElementById('button'); elements.push(element); function removeAllElements() { elements.forEach((item, index) => { document.body.removeChild(document.getElementById(item.id)); elements.splice(index, 1); }); }
由于每个DOM元素也保留对其父节点的引用,因此可以防止垃圾收集器收集元素的父元素和子元素。
总结
在本文中,我们总结了 JS 中内存管理的核心概念。写这篇文章可以帮助我们理清一些我们不完全理解的概念。
希望这篇对你有所帮助,我们下期再见,记得三连哦!
原文地址:https://felixgerschau.com/javascript-memory-management/
作者:Ahmad shaded
译文地址:https://segmentfault.com/a/1190000037651993
更多编程相关知识,请访问:编程入门!!
以上がJavaScriptのメモリ管理を詳しく解説の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

Video Face Swap
完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

人気の記事

ホットツール

メモ帳++7.3.1
使いやすく無料のコードエディター

SublimeText3 中国語版
中国語版、とても使いやすい

ゼンドスタジオ 13.0.1
強力な PHP 統合開発環境

ドリームウィーバー CS6
ビジュアル Web 開発ツール

SublimeText3 Mac版
神レベルのコード編集ソフト(SublimeText3)

ホットトピック









C++ オブジェクト レイアウトとメモリ アライメントにより、メモリ使用効率が最適化されます。 オブジェクト レイアウト: データ メンバーは宣言の順序で格納され、スペース使用率が最適化されます。メモリのアライメント: アクセス速度を向上させるために、データがメモリ内でアライメントされます。 alignas キーワードは、キャッシュ ラインのアクセス効率を向上させるために、64 バイトにアライメントされた CacheLine 構造などのカスタム アライメントを指定します。

C++ のカスタム メモリ アロケータを使用すると、開発者は必要に応じてメモリ割り当て動作を調整できます。カスタム アロケータを作成するには、std::allocator を継承し、allocate() 関数と deallocate() 関数を書き直す必要があります。実際の例としては、パフォーマンスの向上、メモリ使用量の最適化、特定の動作の実装などが挙げられます。使用する場合は、メモリの解放を避けること、メモリのアライメントを管理すること、ベンチマーク テストを実行することなどに注意する必要があります。

マルチスレッド環境では、C++ メモリ管理はデータ競合、デッドロック、メモリ リークなどの課題に直面します。対策には次のものが含まれます: 1. ミューテックスやアトミック変数などの同期メカニズムの使用、 2. ロックフリーのデータ構造の使用、 4. (オプション) ガベージ コレクションの実装。

Go の関数のメモリは値によって渡され、元の変数には影響しません。 Goroutine はメモリを共有し、Goroutine が実行を完了するまで、割り当てられたメモリは GC によって再利用されません。メモリ リークは、完了した Goroutine 参照を保持するか、グローバル変数を使用するか、静的変数を回避することによって発生する可能性があります。リークを回避するには、チャネルを通じてゴルーチンをキャンセルし、静的変数を避け、defer ステートメントを使用してリソースを解放することをお勧めします。

C++ メモリ管理はオペレーティング システムと対話し、オペレーティング システムを通じて物理メモリと仮想メモリを管理し、プログラムにメモリを効率的に割り当ておよび解放します。オペレーティング システムは物理メモリをページに分割し、必要に応じてアプリケーションによって要求されたページを仮想メモリから取得します。 C++ は、new 演算子と delete 演算子を使用してメモリの割り当てと解放を行い、オペレーティング システムからメモリ ページを要求し、それらをそれぞれ返します。オペレーティング システムが物理メモリを解放すると、使用量の少ないメモリ ページが仮想メモリにスワップされます。

参照カウント メカニズムは、C++ メモリ管理でオブジェクト参照を追跡し、未使用のメモリを自動的に解放するために使用されます。このテクノロジはオブジェクトごとに参照カウンタを維持し、参照が追加または削除されるとカウンタが増減します。カウンタが 0 になると、オブジェクトは手動管理なしで解放されます。ただし、循環参照はメモリ リークを引き起こす可能性があり、参照カウンタを維持するとオーバーヘッドが増加します。

PHP 関数でのメモリ使用量を管理するには、不要な変数の宣言を回避し、未使用の変数を解放し、ループと条件を最適化します (無限ループの回避やインデックス付き配列の使用など)。

Go におけるメモリ管理のベスト プラクティスには、メモリの手動割り当てや解放を回避する (ガベージ コレクターを使用する)、オブジェクトが頻繁に作成/破棄される場合のパフォーマンスを向上させるために、参照カウントを使用して共有データへの参照数を追跡する、などがあります。同期メモリ プール sync.Pool は、同時シナリオでオブジェクトを安全に管理します。
