js内存管理
* 内存:由可读写单元组成,表示一片可操作的空间
* 管理:人为的去操作一片空间的申请、使用和释放
* 内存管理:开发者主动申请空间、使用空间、释放空间
* 管理流程:申请——使用——释放
javascript的内存管理
申请内存空间 let obj ={}
使用内存空间 obj.name = 'lg'
释放内存空间 obj = null
js中的垃圾回收
- javascript中内存管理是自动的
- 对象不再被使用时就是垃圾
- 对象不能从根上访问到就是垃圾
javascript 中的可达对象
- 可以访问到的对象就是可达对象(引用,作用域链)
- 可达的标准就是从根出发是否能够被找到
- javascript的根可以理解为全局变量
let obj = {name:'xm'}
let all = obj
obj = null
//obj依然是可达操作 原因是 obj虽然null 了但是all 还依然使用者
垃圾回收的过程演示
function objGroup(obj1,obj2){
obj1.nex1 = obj2;
obj2.prev = obj1;
return{
o1:obj1,
o2:obj2
}
}
let obj = objGroup({name:'obj1'},{name:'obj2'})
console.log(obj)
// 打印结果
{o1:{name:'obj1',next:{name:'obj2',prev:[Circular]}},
o2:{name:'obj2',next:{name:'obj1',next:[Circular]}},
}
可达对象的梳理:
global variable obj———— Object ———o1
global variable obj———— Object ———o2
o1————next————o2
o2————prev————o1
GC定义与作用
- GC 就是垃圾回收的机制的简写
- GC 可以找到内存中的垃圾、并释放和回收空间
GC里的垃圾是什么?
- 程序中不再需要使用的对象
function func(){
name = 'lg'
return `${name} is coder`
}
func();
- 程序中不能再访问到的对象
function func(){
const name = 'lg'
return `${name} is a coder`
}
func()
GC算法是什么
- GC是一种机制,垃圾回收器完成具体的工作
- 工作的内容就是查找垃圾释放空间、回收空间
- 算法就是工作时查找和回收所遵循的规则
常见GC算法
- 引用计数
- 标记清楚
- 标记整理
- 分代回收
引用技数算法
- 核心思想:设置引用数,判断当前引用数是否为0
- 引用计数器
- 引用关系改变时修改引用数字
const user1 = {age:11}
const user2 = {age:22}
const nameList = [user1.age,user2.age]
function fn(){
const num1 = 1
const num2 = 2
}
fn();
fn执行后 num1 num2 不再被引用 所以计数器为0就会被回收
user1 在const之后 nameList中又被使用 所有不被回收
引用技数算法优点/缺点
优点:
- 发现垃圾时立即回收
- 最大限度减少程序卡顿时间 缺点:
- 无法回收循环引用对象
- 时间开销大
// 对象之间循环引用
function fn(){
const obj1 = {}
const obj2 ={}
obj1.name = obj2
obj2.name = obj1
return 'lg is a coder'
}
fn()
标记清除算法实现原理
- 核心思想: 分标记和清除两个阶段完成
- 遍历所有对象找标记活动的对象<和可达对象相同>
- 遍历所有对象清除没有标记对象
- 回收相应的空间
解释: 会遍历所有对象同时会找到他的子对象看是否有所关联
global——A——D
global——B
global——c——E
如果是内部声明的对象例如方法里面使用 c1 别的地方并没有引用 则用完会被清空
标记清除算法优点/缺点
优点:
- 相对于引用计数,解决内部作用域中定义的变量,外部不能访问,内部使用过后被清除(解决循环引用不能回收的问题) 缺点:
- 地址不连续,空间碎片化,浪费空间
- 不会立即执行回收垃圾对象
标记整理算法 工作原理
- 标记整理可以看做是标记清空的增强
- 标记阶段的操作和标记清空一致
- 清除阶段会执行整理 移除对象位置
//标记整理的过程
1]整理那些需要清空和没有引用的数据
2]、进行标记
3]、移动位置将不需要清空的放到一起,需要清空的放到一起
4]、进行清空(都是连续的)
标记清除算法优点/缺点
优点:
- 减少碎片化空间 缺点:
- 不会立即回收垃圾对象
认识V8引擎
- V8是一款主流的javascript执行引擎
- V8采用即时编译
- V8内存设限 (64位不能超过1.5G,32位不超过800M,设置原因是基于web浏览器的这些已经够用了,为了方便垃圾回收)
垃圾回收策略
- 采用分代回收的思想
- 内存分为新生代、老生代
- 针对不同对象采用不同的算法
//过程图
V8内存空间——新生代对象存储——采用具体GC算法
V8内存空间——老生代对象存储——采用具体算法
V8常用的GC算法
- 分代回收
- 空间复制
- 空间清除
- 标记整理
- 标记增量
新生代
v8内存分配
From(新) To 老生代存储(老)
- V8内存空间一分为二(左新,右老)
- 小空间用于存储新生对象(32M | 16M)
- 新生代指的是存货时间比较短的对象
V8如何回收新生代对象
- 回收过程采用赋值算法+标记整理
- 新生代内存区分为两个大小空间
- 使用空间为From,空闲空间为To
- 活动对象存储于From空间
- 标记整理后将活动对象拷贝至To
- From与To交换空间完成释放
回收细节说明
- 拷贝过程中可能出现晋级
- 晋升就是讲新生代对象移动至老生代
- 一轮GC还存活的新生代需要晋升
- To空间的使用率超过25%
老生代
V8如何回收老生代对象
- 老生代对象存放在右侧老生代区
- 64位操作系统1.4G,32位700M
- 老年代对象就是指存货时间较长的对象(例如在全局变量下存在的对象,闭包中存在的对象)
老年代对象回收实现
- 主要采用标记清除、标记整理、增量标记算法
- 首先使用标记清除完成垃圾空间的回收
- 采用标记整理进行空间优化
细节对比
- 新生代区域垃圾回收使用空间换时间
- 老生代区余垃圾回收不适合复制算法
为什么使用Performance
- GC的目的是为了实现内存空间的良性循环
- 良性循环的基石是合理使用
- 时刻关注才能确定是否合理
- Performance提供了多种监控方式
Performance 使用步骤
- 打开浏览器输入目标网址
- 进入开发人员工具面板,选择性能
- 开启录制功能,访问具体界面
- 执行用户行为,一段时间后停止录制
- 分析界面中记录的内存信息
内存问题的体现
- 页面出现延迟加载或经常性暂停
- 页面持续性出现糟糕的性能
- 页面的性能随时间延长越来越差
监控内存的几种方式
界定内存问题的标准
- 内存泄漏: 内存使用持续升高
- 内存膨胀: 在多数设备上都存在性能问题
- 频繁垃圾回收:通过内存变化图进行分析
监控内存的几种方式
- 浏览器任务管理器
- Timeline时序图记录
- 堆快照查找分离DOM
- 判断是否存在频繁的垃圾回收
任务管理器监控内存变化
1]、 正常写功能例如生成1000000长度的数组
2]、打开运行浏览器 调出任务管理器 shift+esc
3]、找到javascript使用的内存
4]、进行对比查看
内存占用空间:原生内存,DOM元素所占用的空间
javascript:js所占用的空间比 (小括号里面显示实际大小如果实际大小实施增加 证明有问题)
什么事分离DOM(堆快照)
- 界面元素存活在DOM树上
- 垃圾对象时的DOM节点
- 分离状态的DOM节点(在节点中已经不再使用,但是子js中还有所引用。所以在js中依然占用内存空间的时候就是内存泄漏)
通过浏览器自带工具,内存建立堆快照进行对比操作
控制台——Memory——建立快照 通过搜索deta查看是否有分离的DOM 可以将分离的DOM通过清理处理
为什么确定是否存在频繁垃圾回收
- GC工作是应用程序时停止的
- 频繁且过长的GC会导致应用假死
- 用户使用中感知应用卡顿
确定频繁的垃圾回收
- Timeline中平凡的上升下降
- 任务管理器中数据不断地增加减少
如何精准测试Javascript性能
- 本质上就是采集大量的执行样本进行数学统计和分析
- 使用基于Benchmark.js 的https:jsperf.com完成
jsperf使用流程
- 使用github账号登录
- 填写个人信息(非必填)
- 填写详细的测试用例信息(title,slug)
- 填写准备代码(DOM操作时经常使用)
- 填写必要的setup与teardown代码
- 填写测试代码片段
为什么要慎用全局变量
- 全局变量定义在全局执行上下问,时所用作用域的顶端
- 全局执行上下文一直存在于上下文执行栈,直到程序退出
- 如果某个局部作用于出现了同名变量则会遮蔽或污染全局
缓存全局变量
- 将使用中无法避免全局变量缓存到局部
// 演示的示例
html
<input type="button" value="btn" id="btn1">
<p>111</p>
<input type="button" value="btn" id="btn2">
script
function getBtn(){
let obt1 = document.getElementById('btn1');
let obt1 = document.getElementById('btn1')
}
function getBtn(){
let obj=document;
let obt1 = obj.getElementById('btn1');
let obt1 = obj.getElementById('btn1')
}
多次用的方法放到原型上
避开闭包陷阱
- 外部具有指向内部的引用
- 在‘外’不做用于访问'内'不做用于的数据
// 演示示例
function(){
var name="lg";
function fn(){
console.log('name')
}
return fn
}
var a = foo()
a();
- 不要为了闭包儿闭包
- 使用闭包需要小心内存泄漏
//示例
function foo(){
var el = document.getElementById('btn');
el.onclick = function(){
console.log(el.id)
}
el = null //如果dom中的元素被删除 这个时候没有清空就会造成 js中还在引用 js中只是减少了一个引用 垃圾回收并没有清空 如果变为null则彻底清除 代码被回收
}
foo();
js中的面向对象
- js不需要属性的访问方法,所有属性都是外部可见的
- 使用属性访问方法只会增加一层重定意,没有访问的控制力
//代码示例
function Age(){
this.age = '12';
}
var xl = new Age();
console.log(xl.age)
function Age(){
this.age = '29'
this.age = function(){
return this.age; //为了性能更好不需要这种访问方式
}
}
var xl = new Age();
xq.age();
for循环优化
//示例
for(let i=0;len = btn.length;i<len;i++){
}
//将len的长度定义缓存 性能更快
采用最优的循环方式
foreach>for>for in 性能排比
节点操作优化
//使用虚拟节点
使用前:
for(var i=0;i<10;i++){
var oP = document.createElement('p');
oP.innerHTML = i
document.body.appendChild(oP)
}
使用后
const fragEle = document.createDocumentFragment();
for(var i=0;i<10;i++){
var oP = document.createElement('p');
oP.innerHTML = i
fragEle.body.appendChild(oP)
}
document.body.appendChild(fragEle)