堆和栈详解js

news2025/1/17 0:03:38

认识堆和栈

学习编程的时候,经常会看到stack这个词,它的中文名字叫做"栈"。

理解这个概念,对于理解程序的运行至关重要。容易混淆的是,这个词其实有几种含义

在理解堆与栈这两个概念时,需要放到具体的场景下去理解。一般情况下有两层含义:

(1)内存操作场景下,堆与栈表示两种内存的管理方式。

(2)数据结构场景下,堆与栈表示两种常用的数据结构。

1、内存操作场景

stack的第一种含义是存放数据的一种内存区域。程序运行的时候,需要内存空间存放数据。一般来说,系统会划分出两种不同的内存空间:一种叫做stack(栈),另一种叫做heap(堆)。

它们的主要区别是:stack是有结构的,每个区块按照一定次序存放,可以明确知道每个区块的大小;

heap是没有结构的,数据可以任意存放。因此,stack的寻址速度要快于heap。

三个变量和一个对象实例在内存中的存放方式如下:

从上图可以看到,i、y和cls1都存放在stack,因为它们占用内存空间都是确定的,而且本身也属于局部变量。但是,cls1指向的对象实例存放在heap,因为它的大小不确定。作为一条规则可以记住,所有的对象都存放在heap。

接下来的问题是,当Method1方法运行结束,会发生什么事?

回答是整个stack被清空,i、y和cls1这三个变量消失,因为它们是局部变量,区块一旦运行结束,就没必要再存在了。而heap之中的那个对象实例继续存在,直到系统的垃圾清理机制(garbage collector)将这块内存回收。因此,一般来说,内存泄漏都发生在heap,即某些内存空间不再被使用了,却因为种种原因,没有被系统回收。

栈由操作系统自动分配和释放,比如基本数据类型(Number、String、Boolean……)和函数的参数值等。

堆由开发人员自主分配和释放,若不主动释放,程序结束时由浏览器回收,用于存储引用类型

2、数据结构场景

JavaScript存在栈和队列概念,通过数组的方式,模仿实现堆栈。

栈:栈是一种运算受限的线性表,其限制是指只仅允许在表的一端进行插入和删除操作,这一端被称为栈顶(Top),相对地,把另一端称为栈底(Bottom)。把新元素放到栈顶元素的上面,使之成为新的栈顶元素称作进栈、入栈或压栈(Push);把栈顶元素删除,使其相邻的元素成为新的栈顶元素称作出栈或退栈(Pop)。通过数组的push()、pop()方法实现栈。

stack的第一种含义是一组数据的存放方式,特点为LIFO,即后进先出(Last in, first out)

与这种结构配套的,是一些特定的方法,主要为下面这些。

push:在最顶层加入数据。
pop:返回并移除最顶层的数据。
top:返回最顶层数据的值,但不移除它。
isempty:返回一个布尔值,表示当前stack是否为空栈。

堆:堆其实是一种优先队列,也就是说队列中存在优先级,比如队列中有很多待执行任务,执行时会根据优先级找优先度最高的先执行。

堆的特点是"无序"key-value"键值对"存储方式。

我们想要在书架上找到想要的书,最直接的方式就是通过查找书名,书名就是我们的key。拿着这把key,就可以轻松检索到对应的书籍。

"堆的存取方式跟顺序没有关系,不局限出入口"

一、js的数据类型

为了更好容易的理解堆和栈,首先来复习一下js中的数据类型。在js中数据类型主要分为以下两大类:

类型

内容

基本类型

String,Number,Boolean,Null,Undefined,这5种基本数据类型它们是直接按值存放的,所以可以直接访问。(简单的数据段,存放在栈内存中,占据固定大小的空间。内存地址大小的固定的,在堆内存中为值分配空间。由于这种值的大小不固定,因此不能把它们保存到栈内存中。但可以将内存地址保存在栈内存中)

引用类型

Function,Array,Object,当我们需要访问这三种引用类型的值时,因为包含引用类型的变量实际上保存的不是变量本身,而是指向该对象的指针。所以,首先得从栈中获得该对象的地址指针,然后再从堆内存中取得所需的数据。(引用类型保存在堆内存中,包含引用类型的变量实际上保存的不是变量本身,而是指向该对象的指针。)

二、什么是堆什么是栈

创建数据的时候就会占用内存、内存主要开辟了两类空间

结构

内容

栈(stack)

由操作系统自动分配内存空间,自动释放,存储的是基础变量以及一些对象的引用变量,占据固定大小的空间。

堆(heap)

由操作系统动态分配的内存,大小不定也不会自动释放,一般由程序员分配释放,也可由垃圾回收机制回收(程序结束时由浏览器回收)。

优缺点:

结构

优点:

缺点:

栈(stack)

  • 栈中的内容是操作系统自动创建、自动回收,占据固定大小的空间,因此内存可以及时得到回收,相对于堆来说,更加容易管理内存空间。

  • 相比于堆来说存取速度会快,并且栈内存中的数据是可以共享的,例如同时声明了var a = 1和var b =1,会先处理a,然后在栈中查找有没有值为1的地址,如果没有就开辟一个值为1的地址,然后a指向这个地址,当处理b时,因为值为1的地址已经开辟好了,所以b也会同样指向同一个地址。

相比于堆来说的缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。

堆(heap)

  • 堆是操作系统动态分配的大小不定的内存,因此方便存储和开辟内存空间。

  • 堆中保存的对象不会自动释放,一般由程序员分配释放,也可由垃圾回收机制回收,因此生存周期比较灵活。

相比于栈来说的缺点是,存在堆中的数据大小与生存期是不确定的,比较混乱,杂乱无章。

总结:

  • 运行程序的时候,每个线程分配一个栈,每个进程分配一个堆,也就是说,stack是线程独占的,heap是线程共用的。此外,stack创建的时候,大小是确定的,数据超过这个大小,就发生stack overflow错误,而heap的大小是不确定的,需要的话可以不断增加。

  • 栈存放基本类型的变量、函数、对象变量指针,堆存放对象

  • 放在栈里面的变量,只要值一样就可以全等,栈占内存较小,会自动释放值,值为null,放在堆里面的变量,值相等(应为会默认转成相同数据类型进行对比),全等=会比较是否引用一个数据故不等,不会自动释放值

  • 基本数据类型,在当前环境执行结束时销毁,而引用类型只有在引用的它的变量不在时,会被垃圾回收机制回收。

三、堆和栈的溢出

(1)如果想要堆溢出,比较简单,可以循环创建对象或大的对象;

(2)如果想要栈溢出,可以递归调用方法,这样随着栈深度的增加,JVM (虚拟机)维持着一条长长的方法调用轨迹,直到内存不够分配,产生栈溢出。

四、 传值和传址

从一个向另一个变量复制引用类型的值,复制的其实是指针,因此两个变量最终指向同一个对象。即复制的是栈中的地址而不是堆中的对象。

从一个变量复向另一个变量复制基本类型的值,会创建这个值的副本。

栈:

let num1 = 1
let num2 = num1
num2 = 2
console.log(num1,num2) // 1,2 变量num1的值并没有因为变量num2值的改变而改变(互不影响)

堆:

let obj1 = {
    name: 'zs',
    age: 20
}
let obj2 = obj1
obj2.age = 22
console.log(obj1.age,obj2.age) // 22,22 对象obj1中的age值因对象obj2的age值的改变而改变

我们常说的值类型和引用类型其实说的就是栈内存变量和堆内存变量,再想想值传递和引用传递、深拷贝和浅拷贝,都是围绕堆栈内存展开的,一个是处理值,一个是处理指针。

当引用数据类型只有一层且需要深拷贝时,有一个特别简单的方式,使用扩展运算符...

let obj1 = {
    name: 'zs',
    age: 20
}
let obj2 = {...obj1}
obj2.age = 22
console.log(obj1.age,obj2.age) // 20,22 
/* 此时的obj2相当于在堆空间新开辟了一块内存把与obj1相同的数据值存储起来,并把这个新的引用地址保存在了一个全新的栈空间中,obj1与obj2互不影响 */

五、为什么会有栈内存和堆内存之分?

通常与垃圾回收机制有关。为了使程序运行时占用的内存最小

当一个方法执行时,每个方法都会建立自己的内存栈,在这个方法内定义的变量将会逐个放入这块栈内存里,随着方法的执行结束,这个方法的内存栈也将自然销毁了。因此,所有在方法中定义的变量都是放在栈内存中的;

当我们在程序中创建一个对象时,这个对象将被保存到运行时数据区中,以便反复利用(因为对象的创建成本通常较大),这个运行时数据区就是堆内存。堆内存中的对象不会随方法的结束而销毁,即使方法结束后,这个对象还可能被另一个引用变量所引用(方法的参数传递时很常见),则这个对象依然不会被销毁,只有当一个对象没有任何引用变量引用它时,系统的垃圾回收机制才会在核实的时候回收它。

垃圾回收

JavaScript堆不需要程序代码来显示地释放,因为堆是由自动的垃圾回收来负责的,每种浏览器中的JavaScript解释引擎有不同的自动回收方式,但一个最基本的原则是:如果栈中不存在对堆中某个对象的引用,那么就认为该对象已经不再需要,在垃圾回收时就会清除该对象占用的内存空间。因此,在不需要时应该将对对象的引用释放掉,以利于垃圾回收,这样就可以提高程序的性能。释放对对象的引用最常用的方法就是为其赋值为null,例如下面代码将newArray赋值为null:

垃圾回收方面,栈内存变量基本上用完就回收了,而推内存中的变量因为存在很多不确定的引用,只有当所有调用的变量全部销毁之后才能回收。

局部环境中,函数执行完成后,函数局部环境声明的变量不再需要时,就会被垃圾回收销毁(理想的情况下,闭包会阻止这一过程)。

全局环境只有页面退出时才会出栈,解除变量引用。所以开发者应尽量避免在全局环境中创建全局变量,如需使用,也要在不需要时手动标记清除,将其内存释放掉。

垃圾回收机制需要跟踪标记变量,并判定是否使用。如何标记未使用的变量也许有不同的实现方式。但是在浏览器里面的话有两种常用的方式:标记清理和引用计数。

标记清理

垃圾回收程序运行的时候,会标记内存中存储的所有变量。然后,它会将所有在上下文中的变量,以及被在上下文中的变量引用的变量的标记去掉。在此之后再被加上标记的变量就是待删除的了,原因是任何在上下文中的变量都访问不到它们了。随后垃圾回收程序做一次内存清理,销毁带标记的所有值并收回它们的内存。

引用计数

思路是对每个值都记录它被引用的次数。声明变量并给它赋一个引用值时,这个值的引用数为1。如果同一个值又被赋给另一个变量,那么引用数加1。类似地,如果保存对该值引用的变量被其他 值给覆盖了,那么引用数减1。当一个值的引用数为0时,就说明没办 法再访问到这个值了,因此可以安全地收回其内存了。垃圾回收程序 下次运行的时候就会释放引用数为0的值的内存。该方法无法解决循环引用问题。如:A引用B,同时B引用A,相互应用。会导致内存泄漏。

总结

  • 离开作用域的值会被自动标记为可回收,然后在垃圾回收期间被删除。

  • 主流的垃圾回收算法是标记清理,即先给当前不使用的值加上标记,再回来回收它们的内存。

  • 引用计数是另一种垃圾回收策略,需要记录值被引用了多少次。

  • 引用计数在代码中存在循环引用时会出现问题。

  • 解除变量的引用不仅可以消除循环引用,而且对垃圾回收也有帮助。

  • 为促进内存回收,全局对象、全局对象的属性和循环引用都应该在不需要时解除引用

内存分配

一般来说栈内存线性有序存储,容量小,系统分配效率高。而堆内存首先要在堆内存新分配存储区域,之后又要把指针存储到栈内存中,效率相对就要低一些了。

内存泄露

是指程序上,动态的分配的堆内存,由于某种原因程序未释放或无法释放,造成系统的浪费,导致程序的运行速度减慢,甚至系统崩溃等严重后果。

JavaScript 内存管理

在内存中共用户使用的内存空间分为3部分:

1.程序存储区

2.静态存储区

3.动态存储区

JavaScript 内存空间分配

栈:变量 基础数据类型,值有固定大小(闭包除外)

堆:复杂的对象 引用数据类型的大小是不固定的,引用数据类型的值保持在堆内存的变量中

池:常量

注:JavaScript不允许直接访问堆内存中的位置实际上在操作对象的引用,而不是实际的对象

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/158822.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于java SSM图书管理系统简单版设计和实现

基于java SSM图书管理系统简单版设计和实现 博主介绍:5年java开发经验,专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 超级帅帅吴 Java毕设项目精品实战案例《500套》 欢迎点赞 收藏 ⭐留言 文末获取源码联系方式…

软件测试/测试开发 | Jenkins通过什么方式报警?

在工作中,一般是没有时间一直看着 Jenkins 直到它运行结果出现的。所以采用了配置 Email 的方式,可以及时将结果通知给我们。 所需要用到的Jenkins插件 需要下载的 Email 插件名称,这两个插件的作用是帮助用户方便的设置格式化邮件&#xf…

【Java集合】开发中如何选择集合实现类

在实际开发中,选择什么集合实现类,主要取决于业务操作的特点,然后根据集合实现类特性进行选择: 👉 先判断存储的类型(一组对象或一组键值对): 一组对象 【单列】:Colle…

ES6-11这一篇就够啦

ES6-11这一篇就够啦ECMAScript 6-111、ECMAScript 相关介绍1.1 ECMAScript简介1.2 ES6的重要性2、ECMAScript 6新特性2.1 let关键字2.2 const关键字2.3 变量的解构赋值2.4 模板字符串2.5 简化对象写法2.6 箭头函数2.7 rest参数2.8 spread扩展运算符2.9 Symbol2.10 迭代器2.11 生…

在GCP上创建GCE的三种方式(Console,gcloud,Terraform)

1 简介 如果要选择GCP为云平台,则经常需要创建GCE(Google Compute Engine),有以下几种方式: (1) 在浏览器创建 (2) 命令 gcloud (3) Terraform 在开始之前,可以查看:《初始化一个GCP项目并用gcloud访问操作》。 …

MATLAB算法实战应用案例精讲-【数据分析】非参数估计:核密度估计KDE

前言 核密度估计(Kernel Density Estmation,KDE)认为在一定的空间范围内,某种事件可以在任何位置发生,但是在不同的地理位置上发生的概率是不一样的,如果在某一区域内其事件发生的次数较多则认为此区域内此事件发生的频率高,反之则低。另外根据地理学第一定律,即:距离…

python开发exe(无GUI)的踩坑笔记

笔者也经常在网上查询信息,但发现很多信息都是照搬,内容甚至有错误,可用性很低.笔者就认为如果要分享就应该把遇到的问题真实的分享出来,让更多同路人少走弯路.节约时间.觉得这篇文章有帮助的同学可以点个赞!将真有用的信息传递给更多人!python开发exe(无GUI)的踩坑笔记pyinsta…

你写过最愚蠢的代码是?

最近写的一些代码,拿出来给大伙看看,毕竟丢的是我的脸。第一个,是帮忙一个朋友看的力扣题目,然后就自己写了下题目如下:https://leetcode.cn/problems/median-of-two-sorted-arrays/代码写成这样void merge(int* nums1…

输入输出系统

文章目录前言前置知识实验操作实验一实验二实验三实验四实验五前言 博客记录《操作系统真象还原》第十章实验的操作~ 实验环境:ubuntu18.04VMware , Bochs下载安装 实验内容: 添加关中断的方式保证原子性。用锁实现终端输出。从键盘获取输…

Docker中的网络模式

使用命令docker inspect 容器id/name能看到容器的ip地址,使用主机和其他容器ping这个地址发现都是可以ping通的,但是使用本地局域网内的其他机器是无法ping通的。 Docker的默认网络模式可以分为:Host 模式、Bridge 模式或者 None 模式。然后来…

word中导入zotero的参考文献

平时使用Zotero管理文献,使用Word写完论文后想用Zotero导入参考文献,也方便修改参考文献格式。 Zotero 打开Zotero找到编辑-首选项 打开首选项,下载国标格式,引用-获取更多样式-搜索框:China Word Word中打开写的…

APSIM练习 :机会种植

该练习是设置一个播种规则,根据条件情况,自动取使用哪种作物进行轮作。 在之前的练习中,我们每年都会重置起始条件。我们不打算在本练习中进行此重置。相反,我们将研究如何根据这些不同的起始条件改变播种的内容;具体…

Docker简介以及安装

官方链接: Docker官网 Docker仓库地址 1、基本要求 docker要求Linux内核系统64位,内核在3.8以上 cat /etc/redhat-release uname -r 2、三要素 2.1、镜像 2.2、容器 2.3、仓库 镜像存放的地方,有点类似Maven仓库 3、安装步骤 官网指导&#xff…

最大比例(数论 最大公约数 辗转相减法)[第七届蓝桥杯省赛C++A/B组]

题目如下: 题解 or 思路: 假设题中所给的数据为 b1,b2,b3,⋅⋅⋅,bnb_1,b_2,b_3,⋅⋅⋅,b_nb1​,b2​,b3​,⋅⋅⋅,bn​,分别用第一项之后的项除以第一项,得到:b2b1,b3b1,⋅⋅⋅,bnb1\frac{b2}{b1},\frac{b3}{b1},⋅⋅…

【Axure教程】自动识别文件类型的上传列表

文件上传是系统中很常用的功能,所以今天作者就教大家在Axure中如何利用中继器,制作一个能自动识别常用的文件类型的上传列表。 一、效果展示 1、点击上传按钮,可以选择本地的文件进行上传 2、选择文件后,在上传列表中新增该文件…

学生用台灯应该选什么样的?看这一篇就够了~

学生在选购一款台灯时,最重要的考虑因素应该是什么?最重要是这款台灯是否真正护眼,价格和外观是次要的,可以根据预算、用途、家居风格来抉择,而是否护眼这一标准需要我们通过衡量台灯的光线指标来判断了。光线指标的五…

ORB-SLAM2 --- LoopClosing::SearchAndFuse函数

目录 1.函数作用 2. code及解析 3. ORBmatcher::Fuse函数解析(闭环调用版) 1.函数作用 将闭环相连关键帧组mvpLoopMapPoints 投影到当前关键帧组中,进行匹配,新增或替换当前关键帧组中KF的地图点。 2. code及解析 /*** brief 将…

第21章 随机游走

第21章 随机游走 随机游走的建模场景是某个对象按照随机选择的方向行走一个步数序列。 21.1赌徒破产 假设一个赌徒一开始有n美元赌注,他要进行一系列1美元投注。如果他赢得一局,则拿回他的赌注外加1美元。如果他输了,那么他将失去1美元。 …

MySQL中InnoDB的事务隔离

文章目录前言一、事务介绍二、事务的四大特性三、事务的隔离性四、事务隔离的实现前言 我们在实际开发中,执行某个业务,肯定不是简单的操作某一句SQL语句,而是多条SQL语句。那么这多条SQL语句必须是全部成功执行,或者全部失败。才…

[L1 - 10分合集]吃鱼还是吃肉

L1-063 吃鱼还是吃肉 分数 10 作者 陈越 单位 浙江大学 题目: 国家给出了 8 岁男宝宝的标准身高为 130 厘米、标准体重为 27 公斤;8 岁女宝宝的标准身高为 129 厘米、标准体重为 25 公斤。 现在你要根据小宝宝的身高体重,给出补充营养的建议…