第一节 内存管理
文章内容输出来源:拉勾教育Java高薪训练营
1.为什么需要内存管理
随着硬件技术不断发展,高级编程语言中都自带了GC垃圾回收机制,这些变化,让我们不需要注意内存空间使用的情况下,也能去完成各种功能开发
- 如下代码从语法角度上没有任何问题
- 但是从内存管理上纯在内存泄漏
- 用Performance查看js Heap会发现蓝色线条持续升高,没有回落
console.log('内存管理');
function fn() {
arrList = []
arrList[100000] = 'lg is codere'
}
fn()
2. 基本介绍
- 内存: 由可读写单元组成,表示一片可操作空间
- 人为去操作一片空间的申请,使用和释放
- 内存管理: 开发者主动申请空间,使用空间,释放空间
- 管理流程: 申请-使用-释放
3. javascript中的内存管理
// 申请
let obj = {}
// 使用
obj.name = "hello"
// 释放
obj = null
4. javascirpt中的垃圾
- javascritp 中的内存管理是自动的
每当我们创建一个对象或者数组等的时候,js引擎都会自动分配内存空间,
后续代码在执行的过程中无法,通过引用关系,无法找到对象的时候,就会被GC自动回收
- 对象不再被引用是垃圾
- 对象不能从根上访问到时是垃圾
4.1 可达对象
- 可以被访问到的对象就是可达对象(引用,作用域链)
- 可达的标准就是从根出发是否能被找到
- javascript中的根就是可以理解为全局变量对象
4.2 javascript 中的引用和可达表现
- 例子1
- 对象{name: ‘test’}被obj引用
- 然后把{name: ‘test’}这个对象的引用赋值给other
- 把obj设置为null
- 虽然obj为null了,但是{name: ‘test’}对象的引用还有other
- 所以{name: ‘test’} 这个对象是可达的
let obj = {name: 'test'}
let other = obj
obj = null
- 例子2 循环引用
所有对象都可以从根上查找
function createObj(obj1,obj2) {
obj1.next = obj2
obj2.prev = obj1
return {
o1: obj1,
o2: obj2
}
}
let obj = createObj({name: 'obj1'},{name: 'obj2'})
console.log(obj);
5. GC算法
- GC是垃圾回收机制的简写
- GC可以找到内存中的垃圾,并释放和回收空间
- GC里的垃圾是什么
- 程序中不在使用的对象
function func() {
const name = 'lg'
return `${name} is a code`
}
func()
- GC是一种机制,垃圾回去器完成具体的工作
- 工作的内容就是查找垃圾释放空间,回收空间
- 算法就是工作时查找和回收所遵循的规则
- 引用计数
通过对对象的引用计数,判断当前对象是否是垃圾
- 标记清除
- 给活动对象添加一个标记,来判断是否是垃圾
- 标记整理
- 碎片化整理
- 分代回收
- v8 根据对象的情况分新生代和老龄代,并有不同的回收机制
5.1 引用计数
5.1.1 思想
- 核心思想: 设置当前对象的引用数,判断当前引用数是否为0
- 引用计数器
- 引用关系改变时,引用计数器主动修改引用数字
- 当引用数为0时,GC开始进行回收工作,并释放空间
当脚本执行完之后
user1/user2/user3因为在全局有ageList引用,所以不会被回收
fn中的num1/num2执行完之后,没有谁引用 他们,所以会被GC回收
const user1 = {age: 11}
const user2 = {age: 12}
const user3 = {age: 13}
const ageList = [user1.age,user2.age,user3.age]
function fn() {
const num1 =1
const num2 =2
}
fn()
5.1.2 优缺点
优点
- 发现垃圾时立即回收
- 当引用数为0时,GC立即开始进行回收工作,并释放空间
- 最大限度减少程序的暂停
- 应用程序执行的过程中必定会对内存进行消耗,当前的平台内存有上限,如果及时回收内存,可以防止内存沾满的时候
缺点
- 无法回收循环引用的对象
如下,虽然方法执行完了,但是obj1和obj2的names属性互相引用对方,所以引用计数的数值并不是为0
所以GC并没有回收它们,造成了空间的浪费
function fn() {
const obj1 = {}
const obj2 = {}
obj1.name = obj2
obj2.name = obj1
return 'test'
}
fn()
- 时间开销大
- 要时刻监控着对象引用数值的变化,如果多个对象需要修改,那么需要的时间就会大一点
5.2 标记清除
5.2.1 思想
- 核心思想: 分标记和清除2个阶段完成
- 遍历所有对象,找到活动对象,然后进行标记操作
- 遍历所有对象,把没有标记的对象进行清除,然后把第一阶段中的对象的标记进行抹除
- 相当于两次遍历把不可达对象就行清除,然后回收空间
有三个全局对象A,B,C,然后在是在每个对象中又递归寻找可达对象,进行标记
如果在某个函数中有a1和b1两个对象,执行完之后,从全局变量出发,没有找到引用的标记
那么这2个对象就会被GC清除,
同时会把清除的空间放到空闲列表中,方便后续程序申请空间使用
5.2.2 优缺点
优点
可以解决循环引用,没有被GC回收的问题
缺点
例如在根下有一个引用的可达对象,也就是红色区域
红色区域左右 两侧的对象,是不可达对象,所以被GC回收,并且空间被添加空闲链表中
后续的程序可以从空闲链表中申请相应大小的空间
问题就来了
一般存储信息分头和域两部分
头是存储数据的元信息
域是存储具体数据的,
虽然空闲链表中有三个域,但是被红色区域分隔开来,地址不连续
如果程序申请的空间大于1个域小于2个域,那么就会出现空间碎片化的问题
空间碎片化
使用标签清除算法之后,回收的空间,在下次程序申请的时候出现,空间大小不匹配的情况,要么出现空间多余,要么出现空间不够
所以就会出现内存空间不能够最大化的使用
标记清除需要遍历2次才能够进行回收,所以不会马上进行回收垃圾,
5.3 标记整理
5.3.1 思想
- 标记整理可以看做是标记清除的增强
- 标记阶段的操作和标记清除一致
- 清除阶段胡先整理,移动对象位置,使空闲空间地址上你能够产生连续
- 整理后会把非活动对象和空闲空间进行整理,让地址连续,然后对内存右侧的空间进行整体回收
这样整理后就不会出现大批量的小空间,后续我们程序申请内存的时候就可以,最大化利用内存空间
5.3.2 优缺点
优点
减少碎片化空间
缺点
不会立即回收垃圾对象