标签:内存安全 代码 ble func 特性 过程 mem 保留 的区别
内存管理是控制
和协调
应用程序访问电脑内存的过程。这个过程是复杂的,对于我们来说,可以说相当于一个黑匣子。
当咱们的应用程序运行在某个操作系统中的时候,它访问电脑内存(RAM)来达成下列几个功能:
上面用来存储程序运行时所需的数据,就是下面要说的堆(heap)和栈(stack)。
顾名思义,是一种先进后出的结构,参考一下餐盘的取和放。
俄罗斯套娃,我这不禁
堆常用来动态内存分配,程序在堆中寻找数据需要使用指针。
内存 | 数据结构 | |
---|---|---|
堆 | new一个对象的引用或地址存储在栈区,指向该对象存储在堆区中的真实数据。由程序员分配和回收 | 是一棵完全二叉树结构 |
栈 | 存储运行方法的形参、局部变量、返回值。由系统自动分配和回收。 | 是一种连续存储的数据结构,特点是存储的数据先进后出 |
与硬盘不同,内存大小有限的,并且往往不是很大。所以说,如果在没有释放无用内存的情况下,无休止的继续消耗内存空间,那么就会造成内存不足甚至操作系统崩溃。。。
所以,以为这种情况,许多语言都提供自动内存管理。并且由于栈是由系统操控,所以我们接下来讨论的自动内存管理主要是堆。
写到这,我想说,我有点不认识堆这个字了。。。
因为现在语言开发者们不想给使用者增加内存管理的负担(或者说是不相信他们能处理好。。),所以设计出自动管理内存的方式。并且很多语言设计出多种方法去自动管理内存,以供开发者选择。下面一一介绍。
语言默认不管理内存,比如C、C++,他们提供malloc、realloc、calloc和free等,但是这不是适用于所有人。
通过释放无用的内存空间去管理堆内存,gc是当前编程中最常见的一种内存自动管理方法,不过大量处理会造成线程卡主,所以利用碎片时间进行。
使用gc的语言有JVM、JavaScript、C#、Golang、OCaml和ruby。
又名: tracing GC。有两个阶段,第一阶段标记那些应该处于活跃状态的数据,下个阶段释放不被引用的数据。
例如JVM、C#、RubyJavaScript和Golang都采用这种方法。
V8引擎还会使用Reference counting GC去弥补,这种gc也可以用于C、C++作为外部库,
引用计数内存管理。使用数字代表当前数据是否可回收,数字会根据引用和失去引用而改变,当为0的时候即被回收。但是这种无法处理循环引用。
由c++之父Bjarne Stroustrup提出,中文翻译为资源获取即初始化。
这种类型的内存管理,对象的内存分配与它的声明周期有关,其核心是把资源和对象的生命周期绑定,对象创建获取资源,对象销毁释放资源。在RAII的指导下,C++把底层的资源管理问题提升到了对象生命周期管理的更高层次。在C++中引入,Ada和Rust也有在用。
自动引用计数。是苹果公司的Objective-C程序的一种自动内存管理机制。
类似于引用计数,但是代替以特定间隔运行,将保留和释放命令插入到代码中,当计数为0时,自动触发, 无需程序暂停。当然ARC依然不能处理循环引用。需要开发者使用某些关键字去处理。
所有权是Rust的突破功能. 它使Rust可以完全内存安全且高效,同时避免垃圾回收。
Rust语言的ownership是rust语言的核心,rust语言之所以被称之为安全的面向系统级别的编程语言 正是由此特性决定的。
到目前为止,我们简单介绍了内存管理的相关内容,每个语言都有特定的机制,统统不同的算法达到不同的目标,接下里我们来聊聊V8.
V8在运行之前将JavaScript编译成了机器代码,而非字节码或是解释执行它,以此提升性能。V8使用C++书写,可以嵌入到任何C++应用程序中。
首先,让我们看下V8引擎内存结构长什么样:
由于js是单线程的,所以node依然会为每个js环境提供单线程环境。如果你在服务端使用,他会为每个服务提供一个进程。在V8程序中,应用程序始终被分配的内存代表。这种内存成为常驻集。如上图所示。
这里用来存储对象和动态数据,这是内存中最大的区域,并且是GC工作的地方。不过,并不是所有的堆内存都可以进行GC,只有新生代和老生代被gc管理。堆可以进一步细分为下面这样:
大对象空间
中分配代码并执行之外的唯一可执行的空间。到目前为止,我们大概了解了内存的各空间组织方式,接下来,让我们看看当程序执行时内存的重要性。
少bb,看代码:
class Employee {
constructor(name, salary, sales) {
this.name = name;
this.salary = salary;
this.sales = sales;
}
}
const BONUS_PERCENTAGE = 10;
function getBonusPercentage(salary) {
const percentage = (salary * BONUS_PERCENTAGE) / 100;
return percentage;
}
function findEmployeeBonus(salary, noOfSales) {
const bonusPercentage = getBonusPercentage(salary);
const bonus = bonusPercentage * noOfSales;
return bonus;
}
let john = new Employee("John", 5000, 5);
john.bonus = findEmployeeBonus(john.salary, john.sales);
console.log(john.bonus);
大白话解释一下:
全局向下文像一个快照一样保存在栈上,每一次函数调用也会将一个快照放到栈中,包括函数的局部变量,参数和返回值。
基本类型保存在栈中,对象、复杂类型或者引用类型保存在堆中。
任何函数的调用都会在栈顶被压入。
一旦主进程完成所有任务,堆中的对象便不会被引用而孤立。
随着我们程序的运行,堆中的数据会越来越多,因为无人看管,栈由系统自动管理,所以无需操心。
所以,为了管理堆空间,垃圾回收机制进场了。
我们知道V8如何分配内存空间,接下来让我们看看V8是如何管理堆空间的。
举个简单的例子,V8会释放被孤立的对象,被孤立的对象一般指,不直接或间接被引用的对象,这样就为新对象腾出了空间。
因此,V8垃圾回收机制代表着:回收未使用内存供V8进行复用。
具体的实现如下:
清道夫GC,主要管理新生代空间,保证新生代空间的紧凑和干净。所有的对象都会分配到新生代空间,新生代空间相对较小。大约在1M ~ 8M,可以通过命令控制。
该GC流程大致是这样:
新生代空间由两个等分的空间组成,to-space和from-space,可以把这两个名字理解为阶段(当前使用
和 当前未使用
),而不是名字。当当前使用
没有更多内存后,触发Minor GC。
比如,当当前使用
没有更多空间了,这时候,新增了一个待分配空间对象,Minor GC会吧当前未使用
切换为当前使用
,然后将之前已满负荷的空间中的数据,根据是否存活整理到新的、空的当前使用
中。存活的就放过去,非存活的就扔掉。并且在移动存活数据的时候,会紧凑摆放,避免空间浪费。最后吧待分配空间对象,分配到新的当前使用
中。
当更多的待分配空间进来时,会重复上面流程,但是如果一个数据存活两次以上,它就会被扔到老生代空间。以保证新生代空间的新
。
上面的过程可以用一个例子来概括:
小明去网吧,第一次去,有位置,直接上机。没位置,得等老板清理下到时间的机器,然后再上机。这种到时间的一般是没办卡的(非存活)。
第二次去,还是上面的流程。
第N次去,小明也办了张卡(存活)
如果小明的卡到期了,那么他也就失活了。
该GC主要负责老生代空间的紧凑和清理。当V8知道老生代空间无更多空间的时候就会触发这个GC。上面的清道夫GC对于体积小的数据来说是完美的,但是对于体积大的来说,非也。一种更好的方式是:标记-扫描-紧凑算法
,顾名思义,一共是三步。
不多解释。自己看图吧。
以上介绍了内存的堆栈,也对比了数据结构的堆栈。
之后简析了几种GC方式和V8的具体实现。
更多深入的内容欢迎交流,互相学习。
memory-management
V8内存简析
内存和数据堆栈
标签:内存安全 代码 ble func 特性 过程 mem 保留 的区别
原文地址:https://www.cnblogs.com/xiaoyuxy/p/12258874.html