ホームページ  >  記事  >  ウェブフロントエンド  >  JavaScript のメモリ管理と GC アルゴリズムについての深い理解

JavaScript のメモリ管理と GC アルゴリズムについての深い理解

WBOY
WBOY転載
2022-07-26 13:50:321972ブラウズ

この記事では、javascript に関する関連知識を提供し、主に JavaScript のメモリ管理と GC アルゴリズムについての深い理解を紹介し、主に JavaScript のガベージ コレクション メカニズムと一般的に使用されるガベージ コレクション アルゴリズムについて説明します。 V8 エンジンのメモリ管理について、皆様のお役に立てれば幸いです。

JavaScript のメモリ管理と GC アルゴリズムについての深い理解

[関連する推奨事項: JavaScript ビデオ チュートリアル Web フロントエンド ]

序文

JavaScript は、変数 (配列、文字列、オブジェクトなど) を作成するときに自動的にメモリを割り当て、使用されないときは割り当てられたコンテンツを「自動的に」解放します。JavaScript 言語は他の基礎となる言語とは異なります。 C 言語などの言語では、必要なメモリ空間を割り当てるために malloc() が使用され、以前に割り当てられたメモリ空間を解放する free() などのメモリ管理インターフェイスが提供されます。

私たちは、

メモリを解放するプロセスをガベージ コレクションと呼んでいます。JavaScript のような高級言語は、メモリ 自動 割り当てと 自動 リサイクルを提供します。これにより、多くの開発者が自動的にメモリ管理を気にしなくなってしまうからです。

高級言語が自動メモリ管理を提供するとしても、内部管理の基本を理解しておく必要があります。自動メモリ管理に問題が発生する場合があります。それをより良く解決するか、それを解決するための最も安価な方法。

メモリ ライフ サイクル

実際、どの言語であっても、メモリ宣言サイクルは大まかに次の段階に分かれています。

以下、各ステップを詳しく説明します:

  • メモリ割り当て: 変数を定義すると、システムは自動的にメモリを割り当て、プログラムがこのメモリを使用できるようにします。
  • メモリ使用量: 変数への 読み書き時に発生
  • メモリ再利用:
  • 使用後、不要なメモリを自動的に解放メモリ、つまり、ガベージ コレクション メカニズムは、使用されなくなったメモリを自動的に再利用します。
  • JavaScript におけるメモリ割り当て

開発者の髪の毛を保護するために、JavaScript メモリ変数を定義すると自動的に割り当てが完了します。サンプルコードは次のとおりです。

let num = 123 // 给数值变量分配内存
let str = '一碗周' // 给字符串分配内存

let obj = {
  name: '一碗周',
  age: 18,
} // 给对象及其包含的值分配内存

// 给数组及其包含的值分配内存(类似于对象)
let arr = [1, null, 'abc']

function fun(a) {
  return a + 2
} // 给函数(可调用的对象)分配内存

// 函数表达式也能分配一个对象
Element.addEventListener(
  'click',
  event => {
    console.log(event)
  },
  false,
)

次のコードのように、メモリが再割り当てされない場合があります。 ##JavaScript でのメモリの使用

##JavaScript で値を使用するプロセスは、実際には、割り当てられたメモリの読み取りと書き込みの操作です。ここでの読み取りと書き込みには、変数の書き込み、変数の値の読み取り、オブジェクトの属性値の書き込み、関数へのパラメーターの受け渡しなどが含まれます。 メモリの解放

JavaScript でのメモリの解放は自動です。解放のタイミングは、特定の値 (メモリ アドレス) が使用されなくなったときであり、JavaScript は占有しているメモリを自動的に解放します。 。

実際、メモリ管理の問題のほとんどはこの段階にあります。ここで最も難しい作業は、不要な変数を見つけることです。

高級言語には独自のガベージ コレクション メカニズムが備わっていますが、現在は多くのガベージ コレクション アルゴリズムがありますが、すべての極端な状況をインテリジェントにリサイクルすることはできません。このため、メモリ管理とガベージ コレクションを学ぶ必要があります。アルゴリズムの理由。

次に、JavaScript のガベージ コレクションと一般的に使用されるガベージ コレクション アルゴリズムについて説明します。

JavaScript のガベージ コレクション

前に述べたように、JavaScript のメモリ管理は自動です。メモリはオブジェクトの作成時に自動的に割り当てられます。オブジェクトが参照されなくなったとき

または

ルートからアクセスできません。ゴミとしてリサイクルされます。

JavaScript の到達可能なオブジェクトは、単に

アクセスできるオブジェクトです。 参照経由であろうとスコープ チェーン経由であろうと、アクセスできる限り、それは到達可能なオブジェクトと呼ばれます。 。到達可能なオブジェクトの到達可能性、つまり、ルートから開始してオブジェクトを見つけることができるかどうかの基準があります。ここでのルートは、JavaScript のグローバル変数オブジェクトとして理解できます。これは、ブラウザ環境では window## です。 #、ノード環境では global です。

引用の概念をよりよく理解するために、次のコードを見てください: <pre class="brush:js;">// 给数组及其包含的值分配内存(类似于对象) let arr = [1, null, &amp;#39;abc&amp;#39;] let arr2 = [arr[0], arr[2]] // 这里并不会重新对分配内存,而是直接存储原来的那份内存</pre>図は次のとおりです:

上の図から、この

{ name: 'A Bowl of Weeks' } はまだゴミとしてリサイクルされないことがわかります。参考に。

次に、到達可能なオブジェクトについて理解しましょう。コードは次のとおりです:

function groupObj(obj1, obj2) {
  obj1.next = obj2
  obj2.prev = obj1

  return {
    obj1,
    obj2,
  }
}
let obj = groupObj({ name: &#39;大明&#39; }, { name: &#39;小明&#39; })

调用groupObj()函数的的结果obj是一个包含两个对象的一个对象,其中obj.obj1next属性指向obj.obj2;而obj.obj2prev属性又指向obj.obj2。最终形成了一个无限套娃。

如下图:

现在来看下面这段代码:

delete obj.obj1
delete obj.obj2.prev

我们删除obj对象中的obj1对象的引用和obj.obj2中的prev属性对obj1的引用。

图解如下:

此时的obj1就被当做垃圾给回收了。

GC算法

GC是Garbage collection的简写,也就是垃圾回收。当GC进行工作的时候,它可以找到内存中的垃圾、并释放和回收空间,回收之后方便我们后续的进行使用。

在GC中的垃圾包括程序中不在需要使用的对象以及程序中不能再访问到的对象都会被当做垃圾。

GC算法就是工作时查找和回收所遵循的规则,常见的GC算法有如下几种:

  • 引用计数:通过一个数字来记录引用次数,通过判断当前数字是不是0来判断对象是不是一个垃圾。
  • 标记清除:在工作时为对象添加一个标记来判断是不是垃圾。
  • 标记整理:与标记清除类似。
  • 分代回收:V8中使用的垃圾回收机制。

引用计数算法

引用计数算法的核心思想就是设置一个引用计数器,判断当前引用数是否为0 ,从而决定当前对象是不是一个垃圾,从而垃圾回收机制开始工作,释放这块内存。

引用计数算法的核心就是引用计数器 ,由于引用计数器的存在,也就导致该算法与其他GC算法有所差别。

引用计数器的改变是在引用关系发生改变时就会发生变化,当引用计数器变为0的时候,该对象就会被当做垃圾回收。

现在我们通过一段代码来看一下:

// { name: &#39;一碗周&#39; } 的引用计数器 + 1
let person = {
  name: &#39;一碗周&#39;,
}
// 又增加了一个引用,引用计数器 + 1
let man = person
// 取消一个引用,引用计数器 - 1
person = null
// 取消一个引用,引用计数器 - 1。此时 { name: &#39;一碗周&#39; } 的内存就会被当做垃圾回收
man = null

引用计数算法的优点如下:

  • 发现垃圾时立即回收;
  • 最大限度减少程序暂停,这里因为发现垃圾就立刻回收了,减少了程序因内存爆满而被迫停止的现象。

缺点如下:

  • 无法回收循环引用的对象;

就比如下面这段代码:

function fun() {
  const obj1 = {}
  const obj2 = {}
  obj1.next = obj2
  obj2.prev = obj1
  return &#39;一碗周&#39;
}
fun()

上面的代码中,当函数执行完成之后函数体的内容已经是没有作用的了,但是由于obj1obj2都存在不止1个引用,导致两种都无法被回收,就造成了空间内存的浪费。

  • 时间开销大,这是因为引用计数算法需要时刻的去监控引用计数器的变化。

标记清除算法

标记清除算法解决了引用计数算法的⼀些问题, 并且实现较为简单, 在V8引擎中会有被⼤量的使⽤到。

在使⽤标记清除算法时,未引用对象并不会被立即回收.取⽽代之的做法是,垃圾对象将⼀直累计到内存耗尽为⽌.当内存耗尽时,程序将会被挂起,垃圾回收开始执行.当所有的未引用对象被清理完毕 时,程序才会继续执行.该算法的核心思想就是将整个垃圾回收操作分为标记和清除两个阶段完成。

第一个阶段就是遍历所有对象,标记所有的可达对象;第二个阶段就是遍历所有对象清除没有标记的对象,同时会抹掉所有已经标记的对象,便于下次的工作。

为了区分可用对象与垃圾对象,我们需要在每⼀个对象中记录对象的状态。 因此, 我们在每⼀个对象中加⼊了⼀个特殊的布尔类型的域, 叫做marked。 默认情况下, 对象被创建时处于未标记状态。 所以, marked 域被初始化为false

进行垃圾回收完毕之后,将回收的内存放在空闲链表中方便我们后续使用。

标记清除算法最大的优点就是解决了引用计数算法无法回收循环引用的对象的问题 。就比如下面这段代码:

function fun() {
  const obj1 = {},
    obj2 = {},
    obj3 = {},
    obj4 = {},
    obj5 = {},
    obj6 = {}
  obj1.next = obj2

  obj2.next = obj3
  obj2.prev = obj6

  obj4.next = obj6
  obj4.prev = obj1

  obj5.next = obj4
  obj5.prev = obj6
  return obj1
}
const obj = fun()

当函数执行完毕后obj4的引用并不是0,但是使用引用计数算法并不能将其作为垃圾回收掉,而使用标记清除算法就解决了这个问题。

マーク アンド クリア アルゴリズムにも欠点があります。このアルゴリズムはメモリの断片化と不連続なアドレスを引き起こします。また、マーク アンド クリア アルゴリズムを使用してガーベジ オブジェクトを見つけたとしても、すぐにクリアすることはできません。もう一度実行してください。クリアします。

マーキングとソートのアルゴリズム

マーキングとソートのアルゴリズムは、マークとクリアのアルゴリズムの拡張版とみなすことができ、そのステップもマーキングとクリアの 2 段階に分かれています。

しかし、マークソートアルゴリズムのクリアフェーズでは、最初にソートし、オブジェクトの位置を移動し、最後にクリアします。

#手順は次のとおりです。

V8 でのメモリ管理

V8 とは

V8 は主流の JavaScript 実行エンジンです。現在、Node.js とほとんどのブラウザーは JavaScript エンジンとして V8 を使用しています。 V8 のコンパイル機能は、動的変換またはランタイム コンパイルとも呼ばれるジャストインタイム コンパイルを使用します。これは、実行前ではなくプログラムの実行中 (実行中) にコンパイルを行うコンピューター コードの実行方法です。

V8 エンジンにはメモリの上限があり、64 ビット オペレーティング システムでは 1.5G、32 ビット オペレーティング システムでは 800 MB が上限です。なぜメモリの上限が設定されているかというと、コンテンツV8エンジンは主にブラウザ向けに用意されており、大規模な空間には向かないことが主な理由であり、もう一つのポイントは、このサイズのガベージコレクションは非常に高速であり、ユーザーがほとんどメモリを使用しないことです。感覚を向上させ、ユーザーエクスペリエンスを向上させます。

V8 ガベージ コレクション戦略

V8 エンジンは世代リサイクルの考えを採用しています。これは主に、特定のルールに従ってメモリを 2 つのカテゴリに分割します。1 つは新世代のストレージ領域で、もう 1 つは新世代のストレージ領域です。もう 1 つは旧世代のストレージ領域です。

新世代のオブジェクトは、生存時間が短いオブジェクトです。簡単に言うと、新しく生成されたオブジェクトです。通常、一定の容量 (64 ビット オペレーティング システムの場合は 32 MB、32 ビット オペレーティング システムの場合は 16 MB) のみをサポートします。古い世代のオブジェクトは、長期間存続するイベントが発生するオブジェクト、またはメモリ内に常駐するオブジェクトです。簡単に言うと、新世代のガベージ コレクション後に生き残るオブジェクトであり、通常、その容量は比較的大きくなります。

次の図は、V8 のメモリを示しています。

V8 エンジンは、さまざまなオブジェクトに基づいてさまざまな GC を使用します。アルゴリズム、V8 で一般的に使用される GC アルゴリズムは次のとおりです:

    ジェネレーション リサイクル
  • スペース コピー
  • マーク クリアリング
  • マーク終了
  • マーク増分
#新世代のオブジェクト ガベージ コレクション

新世代では生存時間が短いオブジェクトが保存されることも上で紹介しました。新世代オブジェクトのリサイクル プロセスでは、コピー アルゴリズムとマーク ソート アルゴリズムが使用されます。

コピー アルゴリズムは、新世代のメモリ領域を同じサイズの 2 つの空間に分割します。現在使用されている状態空間を From 状態と呼び、空間の状態空間を To 状態と呼びます。

下の図に示すように:

#すべてのアクティブなオブジェクトを From スペースに保存します。スペースがいっぱいに近づくと、ガベージコレクション。

まず、新しい世代の From スペースのアクティブ オブジェクトをマークして整理する必要があります。マークが完了すると、マークされたアクティブ オブジェクトは To スペースにコピーされ、マークされていないオブジェクトはリサイクルされます。 、交換用の From スペースと To スペース。

もう 1 つ言及する必要があるのは、オブジェクトをコピーするときに、新しい世代のオブジェクトが古い世代のオブジェクトに移動されることです。

これらの移動オブジェクトには指定された条件があります。主に 2 つのタイプがあります:

    ガベージ コレクションのラウンドを生き残った新しい世代のオブジェクトは、古い世代のオブジェクトに移動されます世代オブジェクト
  • #To 領域占有率が 25% を超える場合、このオブジェクトも古い世代オブジェクトに移動されます (25% の理由は、後続のメモリ割り当てへの影響を避けるためです)
  • #新世代オブジェクトのガベージ コレクション方法は、空間と時間を交換することであることがわかります。
旧世代オブジェクトのガベージコレクション

旧世代領域に格納されるオブジェクトは、生存期間が長く、占有面積が大きいオブジェクトです。生存時間が長く、占有空間が大きいからこそ、レプリケーション アルゴリズムを使用することはできず、もしレプリケーション アルゴリズムを使用すると、長い時間と空間の無駄が発生します。

旧世代のオブジェクトは通常、ガベージ コレクションにマーク クリア、マーク ソート、増分マーキング アルゴリズムを使用します。

クリア フェーズでは、マーク クリア アルゴリズムは主にリサイクルに使用されます。一定の時間が経過すると、多数の不連続なメモリ フラグメントが生成されます。フラグメントが多すぎて十分なメモリを割り当てることができない場合、マーク ソートは私たちのスペースをデフラグするためのアルゴリズム。

古い世代のオブジェクトのガベージ コレクションでは、増分マーキング アルゴリズムを使用してガベージ コレクション プロセスを最適化します。増分マーキング アルゴリズムを次の図に示します。

JavaScript はシングルスレッドであるため、プログラムの実行とガベージ コレクションは同時に 1 つしか実行できません。これにより、ガベージ コレクションの実行時にプログラムがフリーズし、ユーザーに不快な思いをさせることになります。

したがって、増分マーキングが提案されます。プログラムの実行中、プログラムは最初に一定期間実行され、その後、予備マーキングを実行します。このマークは、直接到達可能なオブジェクトのみをマークし、その後、プログラムは続行します。増分マーキングが実行されており、間接的に到達可能なオブジェクトがマークされています。これを最後まで繰り返します。

パフォーマンス ツール

JavaScript はメモリを操作するための API を提供していないため、JavaScript 自体が提供するメモリ管理に依存することしかできませんが、実際のメモリ管理が何であるかはわかりません。のように。場合によっては、メモリの使用状況を監視する必要がある場合があります。パフォーマンス ツールには、メモリを監視するさまざまな方法が用意されています。

パフォーマンスの使用手順

まず、Chrome ブラウザを開き (ここでは Chrome ブラウザを使用しています。他のブラウザも可能です)、アドレス バーにターゲット アドレスを入力して、開発者ツールを開き、[パフォーマンス]パネルを選択します。

パフォーマンス パネルを選択して記録機能をオンにし、特定のインターフェイスにアクセスし、ユーザーを模倣していくつかの操作を実行し、記録を停止します。最後に、分析インターフェイスで記録されたメモリ情報を分析できます。

メモリ問題の症状

メモリ問題の主な症状は次のとおりです:

  • ページの読み込みが遅れているように見えます。頻繁に一時停止され、その最下層は 頻繁なガベージ コレクション の実行を伴います。なぜ頻繁にガベージ コレクションが実行されるのですか? 一部のコードが直接メモリを満杯にし、直ちにガベージ コレクションを必要とする可能性があります。

この問題については、メモリ変更グラフを通じて理由を分析できます。

  • ページのパフォーマンスが低下し続けています。このページを使用していると、これまでにない使いやすさを感じます。通常、ページの下部に メモリ拡張 があると考えられます。いわゆるメモリ拡張です。現在のページは一定の速度を達成するために Yuanda を使用しています。それ自体が必要とするメモリにより、要求されたメモリはデバイス自体が提供できるサイズを超えています。現時点では、パフォーマンスの低下が継続的に発生していることがわかります。

メモリ拡張を引き起こす問題は、コードに問題がある可能性があります。あるいは、デバイス自体の性能が悪い可能性があります。それを分析、特定して解決するには、テストを繰り返す必要があります。複数のデバイス上

  • 時間が経つにつれて、ページのパフォーマンスはますます低下し、読み込み時間はますます長くなります。この問題の原因はコード ## である可能性があります。 #メモリーリーク## #。
  • メモリ リークかどうかを検出するには、
Total Memory View

を通じてメモリを監視できます。メモリが増加し続ける場合は、メモリ リークが発生している可能性があります。

メモリの監視方法

ブラウザでメモリを監視するには、主に以下の方法があります。

    ブラウザが提供するタスク管理デバイス
  • タイムライン シーケンス図
  • ヒープ スナップショットの分離 DOM
  • ガベージ コレクションが頻繁に発生するかどうかの判断
次に、これについて個別に説明します。方法。

タスク マネージャー メモリの監視

ブラウザで [Shift] [ESC] キーを押して、ブラウザーが提供するタスク マネージャーを開きます。次の図は、Chrome ブラウザー マネージャーのタスクを示しています。解釈してみましょう

上の図では、[ナゲット] タブの [メモリ占有スペース] が、ブラウザ内でこのページの DOM が占有しているメモリを表していることがわかります。新しい DOM が作成されており、次の [JavaScript で使用されるメモリ] (デフォルトでは有効になっていないため、右クリックして開く必要があります) は JavaScript のヒープを表し、括弧内のサイズは JavaScript で到達可能なすべてのオブジェクトを表します。

タイムライン レコード メモリ

上記のブラウザーで提供されるタスク マネージャーは、ページに問題があるかどうかを判断するためにのみ使用できますが、ページの問題を特定することはできません。 。

タイムラインはパフォーマンス ツールの小さなタブで、ページ上の状況をミリ秒単位で記録します。これは問題をより簡単に特定するのに役立ちます。

分離された DOM のヒープ スナップショット検索

ヒープ スナップショットは、現在のインターフェイス オブジェクトに分離された DOM が存在するかどうかを非常に的を絞った検索です。分離された DOM が存在するということは、メモリ リークがあることを意味します。

まず、DOM のいくつかの状態を理解する必要があります。

    まず、DOM オブジェクトは DOM ツリーに存在します。通常の DOM
  • DOM ツリーに存在せず、JS 参照がない場合、これはガベージ DOM オブジェクトであるため、リサイクルする必要があります
  • 最後に、そうでない場合は、 DOM ツリーには存在しますが、JS 参照があり、これは分離された DOM なので、手動で解放する必要があります。
DOM をデタッチする手順を見つけます: 開発者ツールを開く → [メモリ パネル] → [ユーザー構成] → [スナップショットの取得] → [フィルター] に

Detached と入力します。分離された DOM、

(以下に示すように):

作成された分離DOMを見つけたら、DOMへの参照を見つけて解放します。

ガベージ コレクションが頻繁に動作しているかどうかを確認する

GC が動作しているときはアプリケーションが停止しているため、現在のガベージ コレクションが頻繁に動作しており、時間が長すぎる場合は、ページが破損します。これは非常に不親切で、アプリケーションが「I」状態にあるように見えるため、ユーザーは使用中にアプリケーションが停止しているように感じます。

ガベージ コレクションが頻繁に発生しているかどうかは、次の方法で判断できます。

  • タイムライン タイミング図 で判断します。現在のパフォーマンス パネルでメモリの傾向を監視します。頻繁に増加と減少が発生すると、ガベージ コレクションが頻繁に発生します。現時点では、コードを見つけて、実行時にこの状況が発生した原因を確認する必要があります。
  • ブラウザタスクマネージャーを使うとより簡単になります。タスクマネージャーの主な変更点は数値です。データは瞬間的に頻繁に増減し、ガベージコレクションも頻繁に行われます。 。
【関連する推奨事項:

JavaScript ビデオ チュートリアル Web フロントエンド ]

以上がJavaScript のメモリ管理と GC アルゴリズムについての深い理解の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はjb51.netで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。