ホームページ > 記事 > ウェブフロントエンド > JavaScript のメモリ管理と GC アルゴリズムについての深い理解
この記事では、javascript に関する関連知識を提供し、主に JavaScript のメモリ管理と GC アルゴリズムについての深い理解を紹介し、主に JavaScript のガベージ コレクション メカニズムと一般的に使用されるガベージ コレクション アルゴリズムについて説明します。 V8 エンジンのメモリ管理について、皆様のお役に立てれば幸いです。
[関連する推奨事項: JavaScript ビデオ チュートリアル 、Web フロントエンド ]
JavaScript は、変数 (配列、文字列、オブジェクトなど) を作成するときに自動的にメモリを割り当て、使用されないときは割り当てられたコンテンツを「自動的に」解放します。JavaScript 言語は他の基礎となる言語とは異なります。 C 言語などの言語では、必要なメモリ空間を割り当てるために malloc() が使用され、以前に割り当てられたメモリ空間を解放する
free() などのメモリ管理インターフェイスが提供されます。
メモリを解放するプロセスをガベージ コレクションと呼んでいます。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 のグローバル変数オブジェクトとして理解できます。これは、ブラウザ環境では window## です。 #、ノード環境では global です。
引用の概念をよりよく理解するために、次のコードを見てください: <pre class="brush:js;">// 给数组及其包含的值分配内存(类似于对象)
let arr = [1, null, &#39;abc&#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: '大明' }, { name: '小明' })
调用groupObj()
函数的的结果obj
是一个包含两个对象的一个对象,其中obj.obj1
的next
属性指向obj.obj2
;而obj.obj2
的prev
属性又指向obj.obj2
。最终形成了一个无限套娃。
如下图:
现在来看下面这段代码:
delete obj.obj1 delete obj.obj2.prev
我们删除obj
对象中的obj1
对象的引用和obj.obj2
中的prev
属性对obj1
的引用。
图解如下:
此时的obj1
就被当做垃圾给回收了。
GC是Garbage collection的简写,也就是垃圾回收。当GC进行工作的时候,它可以找到内存中的垃圾、并释放和回收空间,回收之后方便我们后续的进行使用。
在GC中的垃圾包括程序中不在需要使用的对象以及程序中不能再访问到的对象都会被当做垃圾。
GC算法就是工作时查找和回收所遵循的规则,常见的GC算法有如下几种:
引用计数算法的核心思想就是设置一个引用计数器,判断当前引用数是否为0 ,从而决定当前对象是不是一个垃圾,从而垃圾回收机制开始工作,释放这块内存。
引用计数算法的核心就是引用计数器 ,由于引用计数器的存在,也就导致该算法与其他GC算法有所差别。
引用计数器的改变是在引用关系发生改变时就会发生变化,当引用计数器变为0的时候,该对象就会被当做垃圾回收。
现在我们通过一段代码来看一下:
// { name: '一碗周' } 的引用计数器 + 1 let person = { name: '一碗周', } // 又增加了一个引用,引用计数器 + 1 let man = person // 取消一个引用,引用计数器 - 1 person = null // 取消一个引用,引用计数器 - 1。此时 { name: '一碗周' } 的内存就会被当做垃圾回收 man = null
引用计数算法的优点如下:
缺点如下:
就比如下面这段代码:
function fun() { const obj1 = {} const obj2 = {} obj1.next = obj2 obj2.prev = obj1 return '一碗周' } fun()
上面的代码中,当函数执行完成之后函数体的内容已经是没有作用的了,但是由于obj1
和obj2
都存在不止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 アルゴリズムは次のとおりです:
下の図に示すように:
#すべてのアクティブなオブジェクトを From スペースに保存します。スペースがいっぱいに近づくと、ガベージコレクション。 まず、新しい世代の From スペースのアクティブ オブジェクトをマークして整理する必要があります。マークが完了すると、マークされたアクティブ オブジェクトは To スペースにコピーされ、マークされていないオブジェクトはリサイクルされます。 、交換用の From スペースと To スペース。 もう 1 つ言及する必要があるのは、オブジェクトをコピーするときに、新しい世代のオブジェクトが古い世代のオブジェクトに移動されることです。これらの移動オブジェクトには指定された条件があります。主に 2 つのタイプがあります:
古い世代のオブジェクトのガベージ コレクションでは、増分マーキング アルゴリズムを使用してガベージ コレクション プロセスを最適化します。増分マーキング アルゴリズムを次の図に示します。
JavaScript はシングルスレッドであるため、プログラムの実行とガベージ コレクションは同時に 1 つしか実行できません。これにより、ガベージ コレクションの実行時にプログラムがフリーズし、ユーザーに不快な思いをさせることになります。
したがって、増分マーキングが提案されます。プログラムの実行中、プログラムは最初に一定期間実行され、その後、予備マーキングを実行します。このマークは、直接到達可能なオブジェクトのみをマークし、その後、プログラムは続行します。増分マーキングが実行されており、間接的に到達可能なオブジェクトがマークされています。これを最後まで繰り返します。
JavaScript はメモリを操作するための API を提供していないため、JavaScript 自体が提供するメモリ管理に依存することしかできませんが、実際のメモリ管理が何であるかはわかりません。のように。場合によっては、メモリの使用状況を監視する必要がある場合があります。パフォーマンス ツールには、メモリを監視するさまざまな方法が用意されています。
まず、Chrome ブラウザを開き (ここでは Chrome ブラウザを使用しています。他のブラウザも可能です)、アドレス バーにターゲット アドレスを入力して、開発者ツールを開き、[パフォーマンス]パネルを選択します。
パフォーマンス パネルを選択して記録機能をオンにし、特定のインターフェイスにアクセスし、ユーザーを模倣していくつかの操作を実行し、記録を停止します。最後に、分析インターフェイスで記録されたメモリ情報を分析できます。
メモリ問題の症状
メモリ問題の主な症状は次のとおりです:
この問題については、メモリ変更グラフを通じて理由を分析できます。
メモリ拡張を引き起こす問題は、コードに問題がある可能性があります。あるいは、デバイス自体の性能が悪い可能性があります。それを分析、特定して解決するには、テストを繰り返す必要があります。複数のデバイス上
を通じてメモリを監視できます。メモリが増加し続ける場合は、メモリ リークが発生している可能性があります。
メモリの監視方法ブラウザでメモリを監視するには、主に以下の方法があります。
まず、DOM のいくつかの状態を理解する必要があります。
Detached と入力します。分離された DOM、
(以下に示すように):
作成された分離DOMを見つけたら、DOMへの参照を見つけて解放します。
GC
が動作しているときはアプリケーションが停止しているため、現在のガベージ コレクションが頻繁に動作しており、時間が長すぎる場合は、ページが破損します。これは非常に不親切で、アプリケーションが「I」状態にあるように見えるため、ユーザーは使用中にアプリケーションが停止しているように感じます。
ガベージ コレクションが頻繁に発生しているかどうかは、次の方法で判断できます。
JavaScript ビデオ チュートリアル 、Web フロントエンド ]
以上がJavaScript のメモリ管理と GC アルゴリズムについての深い理解の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。