亚俊 發表於 2022-2-8 02:03:00

JavaScript的内存管理

<h2 id="javascript的内存管理">JavaScript的内存管理</h2>
<h3 id="1什么是内存管理">1.什么是内存管理?</h3>
<blockquote>
<p>在了解JavaScript的内存管理之前,可以先大致熟悉一下什么是内存管理,不管什么样的编程语言,在其代码执行的过程中都是需要为其分配内存的。</p>
</blockquote>
<p>不管什么样的编程语言,以及它用什么方式来管理内存,其内存的管理都具备以下的<strong>生命周期</strong>:</p>
<ul>
<li>申请内存:分配其需要的内存。</li>
<li>使用内存:使用分配的内存。</li>
<li>释放内存:使用完毕后,对其进行释放。</li>
</ul>
<p>但是不同的编程语言对内存的<strong>申请和释放</strong>会有不同的实现,主要分为<strong>手动和自动管理内存</strong>:</p>
<ul>
<li>手动管理内存:像C、C++等一些接近底层的编程语言,都是需要手动来申请和释放内存(malloc函数用于申请内存、free函数用于释放内存)。</li>
<li>自动管理内存:像Java、JavaScript、Python等一些高级编程语言,都是自动帮助我们管理内存的。</li>
</ul>
<h3 id="2javascript的内存分配">2.JavaScript的内存分配</h3>
<blockquote>
<p>通过上面对内存管理的简单介绍可以知道,JavaScript是自动管理内存的,所以在我们编写JS代码定义变量时就会为其分配内存。</p>
</blockquote>
<p>根据JavaScript不同的数据类型,会对其分配到不同的内存空间中,数据类型主要分为<strong>基本数据类型</strong>和<strong>复杂数据类型</strong>:</p>
<ul>
<li>对于基本数据类型的内存分配会在执行时,直接在<strong>栈空间</strong>中进行分配。
<ul>
<li>基本数据类型(也称值类型):string、number、boolean、undefined、null、symbol;</li>
</ul>
</li>
<li>对于复杂数据类型的内存分配会在<strong>堆内存</strong>中开辟一块空间,变量<strong>引用其内存地址</strong>。
<ul>
<li>复杂数据类型(也称引用类型):object、function、array;</li>
</ul>
</li>
</ul>
<p>以下代码在内存结构中的表现形式如下:</p>
<pre><code class="language-js">const name = 'curry'
const age = 30
const info = {
name: 'kobe',
age: 24
}
</code></pre>
<p><img src="https://img2022.cnblogs.com/blog/2506425/202202/2506425-20220208020036336-1567543053.png" alt="" loading="lazy"></p>
<h3 id="3javascript的垃圾回收机制">3.JavaScript的垃圾回收机制</h3>
<blockquote>
<p>在管理内存的生命周期中是包括内存的释放,因为我们的内存大小是有限的,所以当代码执行完毕,不再需要内存的时候,那么就需要对其进行内存释放,以便腾出更多的内存空间给其它的应用程序使用。</p>
</blockquote>
<ul>
<li>而在手动管理内存的编程语言中,需要自己通过一些方式来释放不再需要的内存,这样就需要编写专门用于管理内存的代码,不仅影响编写代码的效率,管理不当也有可能产生<strong>内存泄露</strong>。</li>
<li>所以大部分现代的编程语言都是有自己的垃圾回收机制的,<strong>那么什么是垃圾回收机制?</strong>
<ul>
<li><strong>垃圾回收(Garbage Collection,简称GC)</strong>,就是对于那些不再使用的数据,都可以称之为垃圾,需要通过回收来释放内存空间;</li>
<li>在JavaScript的运行环境JS引擎中就存在垃圾回收的功能模块,这个功能模块就称为<strong>垃圾回收器</strong>;</li>
</ul>
</li>
<li>那么这里就可以提出一个疑问,GC是如何找到不再使用的数据,并对其进行内存回收呢?
<ul>
<li>这里就用到了GC算法,下面介绍<strong>两种常见的GC算法</strong>;</li>
</ul>
</li>
</ul>
<h3 id="4两种常见的gc算法">4.两种常见的GC算法</h3>
<h4 id="41引用计数">4.1.引用计数</h4>
<blockquote>
<p>什么是引用计数?</p>
<p>当一个对象有一个引用指向它时,那么这个对象的引用就加1,并且将其引用次数保存起来,而当一个对象的引用为0时,那么这个对象就可以被销毁了(回收)。</p>
</blockquote>
<p>示例代码:</p>
<ul>
<li>person1的引用次数为3;</li>
<li>person2和person3的引用次为1;</li>
</ul>
<pre><code class="language-js">let person1 = { name: 'curry' }

let person2 = {
name: 'kobe',
friend: person1
}

let person3 = {
name: 'klay',
friend: person1
}
</code></pre>
<p>内存表现:</p>
<p><img src="https://img2022.cnblogs.com/blog/2506425/202202/2506425-20220208020101933-273647136.png" alt="" loading="lazy"></p>
<p>如果接着执行<code>person3 = null</code>,那么person3的引用指向次数就会减1,变为0,从而销毁。而person3销毁后person1也会失去person3的指向,引用指向次数也会减1,变为2。</p>
<p><strong>缺点</strong>:但是引用计数这个GC算法,存在一个很大的弊端,就是当出现<strong>循环引用</strong>时,就无法进行正确的回收,导致内存泄露,如下示例代码:</p>
<ul>
<li>curry的好朋友是klay,巧合的是klay的好朋友是curry,这样就出现了对象的循环引用;</li>
</ul>
<pre><code class="language-js">let person1 = {
name: 'curry',
friend: person2
}

let person2 = {
name: 'klay',
friend: person1
}
</code></pre>
<p>内存表现:</p>
<ul>
<li>即使执行<code>person1 = null; person2 = null</code>,person1和person2对象的引用次数依然为1;</li>
<li>所以引用计数就无法很好的处理这种情况了;</li>
</ul>
<p><img src="https://img2022.cnblogs.com/blog/2506425/202202/2506425-20220208020116688-2071850002.png" alt="" loading="lazy"></p>
<h4 id="42标记清除">4.2.标记清除</h4>
<blockquote>
<p>什么是标记清除?</p>
<p>这个算法设置了一个<strong>根对象(root object)</strong>,GC会<strong>定期</strong>从这个根对象开始往下查找有引用到的对象,而对于那些没有引用到的对象,也就是没有查找到的对象,就认为是需要进行回收的对象。</p>
</blockquote>
<p>标记清除的一大优势就是可以很好的解决循环引用的问题,如下图:</p>
<ul>
<li>标记清除算法首先会从root object往下开始查找引用到的对象;</li>
<li>而对于object6和object7进行了循环引用了的对象,是查找不到的,就会被视为回收对象,从而被GC回收;</li>
<li>目前的JS引擎的GC核心采用的比较多的算法就是标记清除,类似于V8引擎不单单只是用了标记清除,同时也结合了一些其它的算法来应对更多的情况;</li>
</ul>
<p><img src="https://img2022.cnblogs.com/blog/2506425/202202/2506425-20220208020131648-1100338214.png" alt="" loading="lazy"></p><br><br>
来源:https://www.cnblogs.com/MomentYY/p/15869941.html
頁: [1]
查看完整版本: JavaScript的内存管理