概述
区分点
- 有垃圾回收机制:Java、JS、Python、C#
- 无垃圾回收机制:C、C++、Rust
- 垃圾回收机制好处:简化内存管理,提高开发效率
- 无垃圾回收机制好处:语言效率上限更高
C++堆栈
- 栈:存放直接声明创建的数据
- 堆:存放new、malloc创建的数据
- 指针变量:存放数据的内存地址
- 引用变量:起到别名的作用,并没有真正创建一个变量
GC语言堆栈
- 栈:存放基本类型的真实值、引用类型的堆地址
- 堆:存放引用类型的真实值
- 常量池:存放部分常量
- 代码区:存放代码
基本数据类型 | 引用数据类型 | |
存储位置 | 栈 | 堆 |
变量中的值 | 真实数据 | 堆地址 |
读取数据 | 在栈中查找变量 读取变量中的真实数据 | 在栈中查找变量 读取变量中的堆地址 根据堆地址查找引用数据 读取引用数据 |
赋值 | 克隆数据 | 克隆堆地址 |
内存回收
栈内存回收
- 作用域代码运行结束,立即回收栈中的数据
堆内存回收
- 策略一:失去所有引用前,手动释放
- 策略二:失去所有引用后,GC定时回收
- 策略三:失去所有引用后,立即回收
堆内存泄露
- 全局一直引用无用的对象
- 释放内存前,把引用变量弄丢了
Java内存结构
JDK-JRE-JVM
内存图
- 线程栈读取堆中的数据时,会先看缓存区是否已经存在
- 线程栈更新堆中的数据时,会先把更新的数据放入缓存区
- 这种行为会提升性能,但是也产生可见性问题
加载类字节码
- 声明每一个类静态变量和静态方法
- 执行每一个类的静态代码块
JS内存结构
内存图
导入导出
- export 值类型:对外暴露一个数值
- export 引用类型:对外暴露对象的引用
- import 值类型:接收一个数值
- import 引用类型:接收对象的引用
JS垃圾回收
内存泄露
- JS没能回收无法再使用、不会再使用、无需再使用的对象
清除依据
- 引用计数(已废弃):清除引用数为0的对象;不清除引用数大于0的对象;无法将两个相互引用、且不能被其它变量访问的内存对象回收
- 标记清除:标记和清除无法通过全局root访问的对象
两部分
- 新生代:生命周期短,内存空间小
- 老生代:生命周期长,内存空间大
新生代
使用Scavenge算法
- 分区:将新生代内存空间分为两个区域,一半是From空间,一半是To空间。
- 对象分配:新分配的对象首先放入From空间。
- 垃圾回收:当From空间满时,启动垃圾回收。回收过程中,将存活的对象复制到To空间,同时进行标记。不存活的对象将被清除。
- 空间交换:将From空间和To空间的角色交换,以便下一次的分配和垃圾回收。
老生代
同时采用标记-清除和标记-整理的组合
- 标记-清除:标记并清除不再使用的对象;可能导致内存碎片化
- 标记-整理:标记并整理存活的对象;有助于减少内存碎片;引入移动对象的开销,执行速度减慢
晋升
- 初始化的对象通常是新生代
- 新生代对象经历多次垃圾回收后仍存活
- 大概率是对象的生命周期较长或频繁使用
- 会晋升为老生代
定时器
定时器的引用获取不到时,将无法清除定时器
闭包引用
- 函数作用域A引用函数作用域B的数据b,函数作用域B被销毁,但是数据b依旧被缓存
- 函数作用域A被销毁时,数据b才会被回收
回收DOM对象和绑定事件
- DOM元素销毁,绑定的事件也会随之消失
- DOM元素没有被销毁,最好及时把DOM上用不到的事件解绑
- document对象始终存在,所以要及时清除document对象上用不到的事件