前言
- 常网IT源码上线啦!
- 本篇录入吊打面试官专栏,希望能祝君拿下Offer一臂之力,各位看官感兴趣可移步🚶。
- 有人说面试造火箭,进去拧螺丝;其实个人觉得问的问题是项目中涉及的点 || 热门的技术栈都是很好的面试体验,不要是旁门左道冷门的知识,实际上并不会用到的。
- 接下来想分享一些自己在项目中遇到的技术选型以及问题场景。
沟通三要素
1,语速快或慢;2,留点空档给人发言;3,观察变化,适当调整

现场看,还是有点震撼的,致敬。
一、前言
内存管理是很重要的,非常影响性能。
垃圾回收(Garbage Collection,GC)作为自动内存管理机制,通过智能识别和释放不再使用的内存对象,解决了手动内存管理中的常见问题。在浏览器环境中,垃圾回收机制尤为重要,原因有三:
-
安全防护:JavaScript作为动态类型语言,开发者在编码过程中很难精确控制每个对象的生命周期。自动垃圾回收机制有效防止了内存泄漏和野指针等问题。
-
性能优化:单页面应用的内存占用量可达数百MB。高效的垃圾回收能维持浏览器稳定运行,防止页面崩溃。话说最近接手一个react项目,本地运行容易就崩。
-
开发效率:开发者无需手动分配和释放内存,可以更专注于业务逻辑实现。
直入正文。
二、引用标记、标记清除
引用计数法:直观但局限
-
每个对象创建时初始化引用计数器(通常为1)
-
当对象被引用时,计数器增加
-
引用关系解除时,计数器减少
-
当计数器归零时,对象被立即回收
缺陷:循环引用问题。
java
function createCyclicReference() {
const objA = {};
const objB = {};
objA.ref = objB; // objB计数器=1
objB.ref = objA; // objA计数器=1
// 函数结束后,计数器仍为1
// 内存永远无法释放
}
-
循环引用问题:对象相互引用导致计数器永不为零
-
计数器开销:每个对象需要额外存储空间记录引用数
-
实时性陷阱:频繁更新计数器导致性能损耗
标记清除法
三阶段工作流程:
标记阶段(Marking) :
-
从根对象(全局对象、当前函数调用栈等)出发
-
递归遍历所有可达对象并标记为活动状态
-
采用深度优先搜索(DFS)策略
清除阶段(Sweeping) :
-
遍历堆内存中的所有对象
-
回收未被标记的对象所占内存
-
将回收的内存块加入空闲列表
整理阶段(Compacting) :
-
移动存活对象使其在内存中连续排列
-
消除内存碎片,形成大块连续空间
-
更新所有指向移动对象的引用指针
算法优势:
-
完美解决循环引用问题
-
内存利用率显著提高
-
全堆扫描确保无遗漏回收
三、那浏览器的V8用的啥
分代假说
V8的内存管理基于两个关键观察:
新生代(Young Generation) :
-
绝大多数对象生命周期极短
-
约98%的对象在第一次GC时被回收
-
分配速度快但回收频繁
老生代(Old Generation) :
-
存活超过一次GC的对象晋升至此
-
对象存活时间长,回收频率低
-
内存占用大,回收成本高
新生代回收:Scavenge算法
回收过程:
-
遍历From Space中的活动对象(当前对象分配区)
-
将活动对象复制到To Space(上次GC存活对象存放区)
-
复制过程中完成内存整理
-
交换From Space和To Space角色
-
满足晋升条件的对象移至老生代
晋升条件:
-
对象在新生代存活超过两次GC
-
To Space使用率超过25%
-
对象大小超过256KB(直接晋升)
老生代回收:三色标记法
标记优化策略:
-
白色:未访问对象(待回收)
-
灰色:已访问但子对象未完全检查
-
黑色:已访问且子对象全部检查完成
并发回收:无停顿体验
并行标记:
-
主线程与回收线程同时工作
-
多核CPU并行标记不同内存区域
-
回收速度提升3-5倍
并发回收:
-
回收线程独立于主线程运行
-
完全消除用户可感知的停顿
-
需要解决内存访问冲突问题
并行回收:牺牲内存空间换取时间效率
四、内存泄漏
- 意外全局变量:
java
function leakMemory() {
leakedData = new Array(1000000); // 未声明变量
}
- 遗忘的定时器:
java
const timer = setInterval(() => {
const data = loadData(); // data被持续引用
updateUI(data);
}, 1000);
// 忘记clearInterval(timer)
- DOM引用残留:
java
const elements = {
button: document.getElementById('myBtn'),
container: document.getElementById('container')
};
// 移除DOM但未删除引用
document.body.removeChild(elements.container);
诊断工具链
Performance Monitor:
-
实时监控JS堆大小
-
观察GC频率及时长
Memory面板:
-
Heap Snapshot:堆内存快照
-
Allocation Timeline:内存分配时间线
-
Allocation Sampling:分配采样分析
内存分析指标:
-
JS Heap Size:JavaScript对象占用内存
-
DOM Nodes:DOM节点数量
-
Event Listeners:事件监听器数量
-
GPU Memory:显存使用量
五、Good Code
- 对象池技术:
java
class ObjectPool {
constructor(createFn) {
this.pool = [];
this.createFn = createFn;
}
acquire() {
return this.pool.pop() || this.createFn();
}
release(obj) {
this.pool.push(obj);
}
}
// 使用示例
const vectorPool = new ObjectPool(() => ({x:0, y:0}));
const v1 = vectorPool.acquire();
// 使用后归还
vectorPool.release(v1);
- 避免内存抖动:
对整个对象进行重新赋值 ~ 谨慎
java
// 反例:频繁创建临时对象
function updatePositions() {
objects.forEach(obj => {
obj.position = {x: Math.random(), y: Math.random()};
});
}
// 正例:复用内存
function updatePositions() {
objects.forEach(obj => {
obj.position.x = Math.random();
obj.position.y = Math.random();
});
}
至此撒花~
后记
知道之后,有了更深刻的认识。
明白了时间的价值:用户可感知的停顿与流畅体验,还有算法的精妙。
正如V8引擎首席架构师所说:"垃圾回收不是科学,而是艺术------在约束中创造自由,在混沌中建立秩序"。
我们在实际项目中或多或少遇到一些奇奇怪怪的问题。
自己也会对一些写法的思考,为什么不行🤔,又为什么行了?
最后,祝君能拿下满意的offer。
我是Dignity_呱,来交个朋友呀,有朋自远方来,不亦乐乎呀!深夜末班车
👍 如果对您有帮助,您的点赞是我前进的润滑剂。
以往推荐
大白话学习性能优化requestAnimationFrame
vue2和Vue3和React的diff算法展开说说:从原理到优化策略