Title: js没那么简单(3)--内存模型 · Issue #16 · wython/wython.github.io · GitHub
Open Graph Title: js没那么简单(3)--内存模型 · Issue #16 · wython/wython.github.io
X Title: js没那么简单(3)--内存模型 · Issue #16 · wython/wython.github.io
Description: 前言 js的内存模型相对比较简单,可以简单的分为堆内存和栈内存。本节主要讨论,js的内存模型,以及js如何做垃圾回收的。因为垃圾回收,其实对闭包有一定的思考意义。当然,我相信并不是所有人都能认识到这点。 内存模型 内存模型是代码中对硬件运行环境的一个抽象,其可以表示为执行过程中,变量和数据在实时内存中的一个表现。 在历史长河中,语言对内存对分配大体可分为三种,静态分配,堆分配,栈分配。 静态分配 静态分配是最早出现的内存分配模式。我们可以简单理解为,代码需要在最开始时候...
Open Graph Description: 前言 js的内存模型相对比较简单,可以简单的分为堆内存和栈内存。本节主要讨论,js的内存模型,以及js如何做垃圾回收的。因为垃圾回收,其实对闭包有一定的思考意义。当然,我相信并不是所有人都能认识到这点。 内存模型 内存模型是代码中对硬件运行环境的一个抽象,其可以表示为执行过程中,变量和数据在实时内存中的一个表现。 在历史长河中,语言对内存对分配大体可分为三种,静态分配,堆分配,栈分配。 静态...
X Description: 前言 js的内存模型相对比较简单,可以简单的分为堆内存和栈内存。本节主要讨论,js的内存模型,以及js如何做垃圾回收的。因为垃圾回收,其实对闭包有一定的思考意义。当然,我相信并不是所有人都能认识到这点。 内存模型 内存模型是代码中对硬件运行环境的一个抽象,其可以表示为执行过程中,变量和数据在实时内存中的一个表现。 在历史长河中,语言对内存对分配大体可分为三种,静态分配,堆分配,栈分配。 静态...
Opengraph URL: https://github.com/wython/wython.github.io/issues/16
X: @github
Domain: patch-diff.githubusercontent.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"js没那么简单(3)--内存模型","articleBody":"### 前言\r\njs的内存模型相对比较简单,可以简单的分为**堆内存**和**栈内存**。本节主要讨论,js的内存模型,以及js如何做垃圾回收的。因为垃圾回收,其实对闭包有一定的思考意义。当然,我相信并不是所有人都能认识到这点。\r\n\r\n### 内存模型\r\n内存模型是代码中对硬件运行环境的一个抽象,其可以表示为执行过程中,变量和数据在实时内存中的一个表现。\r\n\r\n在历史长河中,语言对内存对分配大体可分为三种,**静态分配**,**堆分配**,**栈分配**。\r\n\r\n##### 静态分配\r\n静态分配是最早出现的内存分配模式。我们可以简单理解为,代码需要在最开始时候就确定所使用的内存空间,并且所占用的空间是固定的。这样既不需要内存执行时创建,也不需要摧毁,效率更高。当然,灵活性方面也很差,对于我们现在高级的语言来说。这种方式显得笨拙。\r\n\r\n##### 栈分配\r\n栈分配是后来演变出来的一种比较灵活的内存分配方式。如果你记得前面讲到的动态作用域语言,那么你可以理解到,对于一开始只有栈分配类型的语言,他的作用域是跟着其内存分配走的,意思是:这种类型但分配语言,每次运行时,都是以一种**活动记录**方式压入系统栈。活动记录在你可以看成类似js执行上下文的东西。也就是说,这种分配方式特点是:\r\n1. 程序所涉及到的内存需要提前预知,在运行时固定大小的内存作用类似栈结构的数据结构。\r\n2. 由于其作用域是跟着活动记录存活走,固栈分配的内存摧毁会根据活动记录的销毁而销毁\r\n\r\n##### 堆分配\r\n堆分配相对栈分配则更加灵活,堆分配克服了栈分配内存无法动态分配的缺陷。同时,堆分配使得作用域灵活性更高,比如js的闭包,在栈结构的设计中是无法达成的,但是用堆内存分配之后,闭包具有更灵活的生命周期,而非跟着执行上下文周期去走。\r\n\r\n这种动态内存分配的特性,必然导致了很多其他问题的出现,比如,计算机不能像栈内存那样去确定该内存的存活周期。你可以理解为:堆内存的变量我们无法知道其该在什么时候摧毁。栈内存之所以简单在于其变量是跟着活动记录走。所以堆内存的出现,在c++这种语言中,需要人工分配,人工去释放。这样的意思是:程序对于自己分配的内存具有自己把握的权利,但是对于程序员自己分配的内存,只有程序员知道什么时候可以摧毁。\r\n\r\n后来,类似java这种,借助算法的实现,从而可以固定时间去确定某些变量已经无法继续使用。从而用算法方式去回收它们。这正是我们后面讨论的垃圾回收算法。\r\n\r\n#### js内存模型\r\n在c++中,c++的内存模型会相对更复杂,会有全局静态存储区,堆栈等等。对于js来说,其模型会相对简单很多。可以简单分为**堆内存**和**栈内存**。\r\n\r\n我们可以简单看看这张图片:\r\n\r\n\r\n##### 栈空间(Stack)\r\n在v8中,每一个js进程会有自己管辖的栈空间,内存中的栈空间可以抽象成类似数据结构的后进先出的结构。这样的内存空间,在物理上表现为**连续固定的区域**,在运行时表现为后进先出。比如我们之前提到执行上下文的概念,可以认为是对整个块环境的一个完整处理,那执行上下文中的数据以栈分配形式存储。\r\n\r\n而在实际v8的运行过程中,栈空间的处理是类似于游标的指针决定。类似于下图的游标,很多时候我们对内存对处理不会像现实中那样,真正意义上对清理,游标下移,表示游标以上对空内存为空闲,这时候可以认为其已经出栈。在程序中,我们可以在用到时候覆盖这块内存,所以不需要显式处理\r\n\r\n\r\n##### 堆空间(Heap)\r\n在v8中,对堆空间堆划分会比较详细很多,这也难怪,堆是具有灵活多变堆一种内存分配方式。在物理上表现为**不一定连续不固定大小区域**,在运行时,它可以根据代码上需要的空间,动态分配需要的内存。同时,最大的特点和问题,也是我们经常关注的内存回收问题。我们知道,在c++中,部分人为堆内存的分配是由人自己去释放。这是因为堆的灵活性,导致其回收也具有一定灵活性,无法像栈结构那样有确定的生命周期。\r\n\r\n而在js中,语言的灵活性更高。好在js有灵活的算法去确定其是否需要回收,并且定时进行回收。\r\n\r\n\r\n按v8的堆内存做划分:\r\n- **新生代(new space)**:新生代和老生代是我们的js代码所能分配的空间,是垃圾回收主要关注的区域,新生代主要存生命周期比较短的变量和内容。所以空间较小,但是变更频繁。\r\n- **老生代(old space)**:老生代是生命周期较长的分配区域,空间大,不过相对变化比较不频繁。新生代和老生代在后面垃圾回收会主要讲解,他们垃圾回收关注的主要区域。\r\n- **大对象空间(Large object space)**:超过内存限制的大对象会在这里开辟空间。\r\n- **代码空间(Code-space)**:在我们前面提到,执行上下文中,会有些编译后的二进制可执行代码,函数的过程代码,大部分会存储在这个区域。有一部分也会存储在大对象空间。\r\n- **细胞空间,属性细胞空间,map 空间(Cell space, property cell space, and map space)**:这些空间分别包含:Cells, PropertyCells, 和 Maps的数据结构。这个空间的大小结构是固定的。\r\n\r\n#### js数据类型和内存模型的关系\r\n\r\n通过上面的讨论,我们对内存对模型有个基本的认识。但是所有的知识,不能脱离js本身。那么在js中,我们对数据对使用,在内存中又是如何具体体现对呢?\r\n\r\n首先,我们得知道js的数据类型有哪些,在js中,有八种基本的数据类型:\r\n\r\n1. **Boolean**: true或者false\r\n2. **Number**: 数字类型,固定64位空间。\r\n3. **String**: 字符串类型\r\n4. **Undifined**:一个没有值的空间,默认会是undifined。前面提到的变量提升之后,由于变量没有具体执行的内容,表现即为undifined。\r\n5. **Null**: 就是null\r\n6. **Symbol**: es6中新引入的一种类型\r\n7. **BigInt**: 具有大空间的整型\r\n8. **Object**: 引用类型基类,我们称它对象,函数,数组等特殊结构都是以它实现的。\r\n\r\n在这八种类型中,Object我们叫**引用类型**,其他七种叫**基本数据类型**。这么区分的原因在于他们在内存分配上的区别导致的。\r\n\r\n我们先简单的说,基本类型是存储在栈结构中,而引用类型存储在堆结构中。为什么要这么去划分,这是一个值得思考的问题。\r\n这是因为,基本结构具有可提前预知,固定的数据大小,所以可以满足用栈存储的前提条件。而引用类型,他们的大小是会动态变化的,我们知道,堆的优点就是动态分配,所以引用类型需要借助堆动态分配的方式,灵活的变化空间。\r\n\r\n这里我们用具体代码来看看:\r\n\r\n```javascript\r\nvar a = 100\r\na = a + 100\r\n\r\nvar b = []\r\nb.push(1)\r\n```\r\n\r\n上面代码中,我们知道,对于number类型来说,64位固定大小空间,无论是100,还是200,都是用64位表示。所以当我们如果数字较大,就会溢出。这其实是固定位数大小的体现。而对于array类型来说,其存储的空间会不断真正意义上的变化。比如十个长度的数组和20个长度的数组其占用空间就不一样。\r\n\r\n如此,array只能用堆动态分配才能满足其要求。\r\n\r\n接下来,我们在看看两种不同类型如何用体现:\r\n\r\n\r\n对于栈空间,变量a是基本数据类型,所以具有唯一内存标识,对应在栈里面的一个值。而对于变量b,执行上下文也会为其分配一定的栈内存,不过存储的是指向堆的一个内存区域,用于数组的存储,该数组是动态分配的。这样很好的看出,基本类型和引用类型在存储上的区别了。\r\n\r\n#### 包装类型\r\n看过高级程序设计的书的同学应该知道,基本类型具有如引用类型一样特殊的方法,比如toString方法。相信大部分人,不会去过多思考这个东西,也许你们只是简单了解到它的特性,比如包装的周期很短,仅仅在于用到的语句间存活。但是其实包装类型体现了更多有趣的东西,正好验证了我们上面的说法。\r\n\r\n这里先回顾下包装类型的特点\r\n\r\n我们知道,基本类型都具有自己的构造函数,这个构造函数的原型是Object。所以他们的实例具有Object的方法,也具有一些自身的特殊方法。\r\n\r\n但是我们前面又说到,基本类型是存储在栈内存,它的大小是固定的,它不应该有类型Object这种面向对象方式的灵活方法。这似乎是矛盾的,一方面它表现像实例,一方面它又是固定结构。\r\n\r\n只有在这个层面上思考,你才真正能理解到包装类型的本质,包装类型的意思,真是如它名字表示的,包装。也就是说,在运行的时候,如果基本类型用了如对象一样的函数调用,那么会把基本类型做一个包装,让他表现的像一个对象。而与我们自己使用的对象类型不同,由于这个包装是编译器自己产生的,所以编译器会在其使用完之后,立即摧毁掉对应的对象。\r\n\r\n**如何理解这个行为,我们知道,垃圾回收是比较复杂的,对于这种编译器自己具有控制权,并且知道生命周期的堆内存空间,这种用完立即摧毁的方式是理所当然的。**\r\n\r\n我们可以用代码简单的去抽象这个行为:\r\n\r\n```javascript\r\nvar a = 1\r\n\r\nconsole.log(a.toString())\r\n\r\na = a + 1\r\n\r\n// 以上代码会转化称\r\n\r\nvar a = 1\r\n\r\nvar tem = a // 包装\r\na = new Number(tem) // 包装\r\nconsole.log(a.toString()) // 包装\r\na = tem // 包装\r\n\r\na = a + 1\r\n```\r\n\r\n正如上面代码,a在用到toString的时候会进行包装,但是用完也会立即回收Number类型的包装。这样有利于更好利用堆空间。这里希望你在细细体会。\r\n\r\n另外,在ts中,会对包装类型和基本类型做区别,体现为number和Number的类型。\r\n\r\n最后,由于篇幅的原因,垃圾回收就放后面讲了,这里就先提出一个内存模型概念就好了。end\r\n\r\n相关题目:\r\n1. 1 为什么没有toString方法\r\n2. 1 为什么能调用toString方法\r\n3. 1 和 new Number(1)在内存占用上有什么区别\r\n4. Number(1) 和 new Number(1) 的区别\r\n5. ts中Number和number类型有什么区别","author":{"url":"https://github.com/wython","@type":"Person","name":"wython"},"datePublished":"2020-08-20T02:30:29.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":0},"url":"https://github.com/16/wython.github.io/issues/16"}
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:58d9ba20-79cf-8db1-bc2a-58a504c0718a |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | A98A:1BDD2D:1554437:1B38F31:69756277 |
| html-safe-nonce | 076dff3133882543180b2486731b1d5ec66f897694a3a20c3c296443473b745c |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJBOThBOjFCREQyRDoxNTU0NDM3OjFCMzhGMzE6Njk3NTYyNzciLCJ2aXNpdG9yX2lkIjoiNDkwMDk0ODIwMjExMTcyMjEwNCIsInJlZ2lvbl9lZGdlIjoiaWFkIiwicmVnaW9uX3JlbmRlciI6ImlhZCJ9 |
| visitor-hmac | 8839222b7a6df43b91389d7f225a192913135beae968c1e75f9bd0f2abc86d62 |
| hovercard-subject-tag | issue:682317873 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/wython/wython.github.io/16/issue_layout |
| twitter:image | https://opengraph.githubassets.com/05feb806f27dc1d15766186b67197aaf394071d55a6096553001d921b24d57e4/wython/wython.github.io/issues/16 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/05feb806f27dc1d15766186b67197aaf394071d55a6096553001d921b24d57e4/wython/wython.github.io/issues/16 |
| og:image:alt | 前言 js的内存模型相对比较简单,可以简单的分为堆内存和栈内存。本节主要讨论,js的内存模型,以及js如何做垃圾回收的。因为垃圾回收,其实对闭包有一定的思考意义。当然,我相信并不是所有人都能认识到这点。 内存模型 内存模型是代码中对硬件运行环境的一个抽象,其可以表示为执行过程中,变量和数据在实时内存中的一个表现。 在历史长河中,语言对内存对分配大体可分为三种,静态分配,堆分配,栈分配。 静态... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | wython |
| hostname | github.com |
| expected-hostname | github.com |
| None | 4a4bf5f4e28041a9d2e5c107d7d20b78b4294ba261cab243b28167c16a623a1f |
| turbo-cache-control | no-preview |
| go-import | github.com/wython/wython.github.io git https://github.com/wython/wython.github.io.git |
| octolytics-dimension-user_id | 15258919 |
| octolytics-dimension-user_login | wython |
| octolytics-dimension-repository_id | 142893945 |
| octolytics-dimension-repository_nwo | wython/wython.github.io |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 142893945 |
| octolytics-dimension-repository_network_root_nwo | wython/wython.github.io |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 488b30e96dfd057fbbe44c6665ccbc030b729dde |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width