乐趣区

前端初级阶段51620

内容

1. 内存泄漏与垃圾回收
2.cookie 和 session
3. 单线程原理
4. 上下左右居中的几种实现。5.BFC 和 IFC 模型。

一、垃圾回收与内存泄漏

参考:内存控制

1. 垃圾回收

v8 的垃圾回收策略主要基于分代式垃圾回收机制。按照对象的存活时间将内存的垃圾回收进行不同的分代,然后,分别对不同的分代的内存再进行高效的垃圾回收算法。在 V8 中,主要将内存分为新生代和老生代两代。新内存中的对象存活时间短,老内存中的对象存活时间长或常驻内存对象。

1)新生代垃圾回收算法 scavenge 算法

新生代中的对象主要通过 scavenge 算法进行垃圾回收,其主要是采用 cheney 算法进行具体处理。

cheney 算法采用一种复制方式的垃圾回收算法,将堆内存一分为二,只有一部分空间被使用称为 From 空间,另一个处于闲置称为 To 空间。当进行分配对象的时候先在 from 空间分配,当进行垃圾回收时,会检查 from 空间中的存活对象,将这些存活对象复制到 to 空间中, 复制完成后 From 和 to 空间角色互换,清空 to 空间,在垃圾回收过程中就是通过将存活对象在两个空间中进行复制。

  • 缺点:只能使用一半的内存
  • 优点:只复制存活的对象,对于生命周期短的场景存活对象只占小部分,所以时间效率高

当一个对象经过多次复制依然存活时,就会被认为是生命周期较长的对象,会被移入老生代内存中。

对于移入老生代内存有两个条件:

  • 对象已经经过新生代内存回收机制的回收依然存活
  • 复制到 To 空间的对象超过 25%(为什么是 25%?这个 To 空间接下来会成为 From 空间并接受内存分配,如果占比过高影响后续分配)

2)老内存垃圾回收算法 Mark-Sweep & Mark-Compact

老内存中,大多是不死的老对象,用 scavenge 算法又费力,又占用空间,因此,采用了新的内存垃圾回收算法:Mark-Sweep & Mark-Compact。

Mark-Sweep 标记清扫

mark-sweep 分为标记和清除两个阶段,mark 阶段会遍历堆,然后标记处活着的对象,sweep 阶段会清除没有被标记的对象。mark-sweep 只清理没有标记的对象,在老内存中,死了的对象占比较少,这也是这个算法高效的原因。

mark-sweep 的问题在于,每次 sweep 后,会存在内存碎片,这些不连续的内存碎片会占有大量空间,因此,下一次复制大对象时,将会发现空间不够,因而再次触发垃圾回收,这个回收是不必要的,也浪费了 cpu。为了解决这个问题,增加了 mark-compact 算法。

mark-compact 标记整理和压缩
mark-compact 在整理过程中,将活着的对象往一端移动,移动完成后,直接将另外一端的内存清理掉。
因为 mark-compact 需要移动内存,因此,垃圾回收主要使用 mark-sweep,在内存不够时,才会触发一次 mark-compact。

这三种算法的比较:

Incremental Marking
为了避免出现 javaScript 应用逻辑与垃圾回收器看到不一致的情况,垃圾回收都要将应用逻辑停下来,这种行为会造成停顿,在新生代垃圾回收过程中因为存活对象比较少,即使停顿基本影响不大。在老生代垃圾回收中,通常存活对象较多,全堆垃圾回收的标记、清除、整理影响较大。
解决办法:分批次进行,拆分成许多小步,每进行一小步就让逻辑运行一会。

v8 后续还引入了 lazy sweeping 与 incremental compaction,同时还引入了,并行标记和并行清理,进一步的利用多核性能降低每次停顿的时间。

2. 内存泄漏

内存泄漏的实质就是应当回收的对象因为意外没有被回收,变成了常驻在老生代中的对象。
造成内存泄漏的主要原因有:缓存、队列消费不及时、作用域未释放。

1)缓存
慎将内存当做缓存,一旦一个对象被当做缓存来使用,那它将会常驻在老生代中,这将导致垃圾回收在进行扫描和整理时,对这些对象做无用功。
v8 内存是通过垃圾回收进行处理的,没有过期策略,而真正的缓存是存在过期策略的。
缓存限制策略:将结果记录在数组中,一旦超过数量,就以先进先出的方式进行淘汰。

2)闭包
闭包是通过中间函数进行间接访问内部变量实现的一个功能,一旦变量引用这个中间函数,这个中间函数将不会释放,同时也会使原始的作用域不会得到释放,作用域中产生的内存占用也不会得到释放。除非不再有引用,才会逐步释放。

二、cookie 和 session

参考:构建 Web 应用

1.cookie

http 是一个无状态的协议,现实中的业务却是需要有状态的,否则无法区分用户之间的身份。利用 cookie 记录浏览器与客户端之间的状态。
cookie 的处理分为如下几步:

  • 服务器向客户服务发送 cookie
  • 浏览器将 cookie 保存
  • 之后每次浏览器都会将 cookie 发送给服务器,服务器端再进行校验

告知客户端是通过响应报文实现的,响应的 cookie 值在 set-cookie 字段中,它的格式与请求中的格式不太相同,规范中对它的定义如下:

Set-Cookie: name=value; Path=/; Expires=Sun, 23-Apr-23 09:01:35 GMT; Domain=.domain.com;

name = value 是必选字段,其他为可选字段。

可选字段 说明
path 表示这个 cookie 影响的路径,当前访问的路径不满足该匹配时,浏览器则不发送这个 cookie
Expires、Max-Age 用来告知浏览器这个 cookie 何时过期的,如果不设置该选项,在关闭浏览器时,会丢失掉这个 cookie,如果设置过期时间,浏览器将会把 cookie 内容写入到磁盘中,并保存,下次打开浏览器,该 cookie 依旧有效。expires 是一个 utc 格式的时间字符串,告知浏览器此 cookie 何时将过期,max-age 则告知浏览器,此 cookie 多久后将过期。expires 会在浏览器时间设置和服务器时间设置不一致时,存在过期偏差。因此,一般用 max-age 会相对准确。
HttpOnly 告知浏览器不允许通过脚本 document.cookie 去更改这个 cookie 值,也就是 document.cookie 不可见,但是,在 http 请求的过程中,依然会发送这个 cookie 到服务器端。
secure 当 secure = true 时,创建的 cookie 只在 https 连接中,被浏览器传递到服务器端进行会话验证,如果 http 连接,则不会传递。因此,增加了被窃听的难度。

cookie 的性能影响

当 cookie 过多时,会导致报文头较大,由于大多数 cookie 不需要每次都用上,因此,除非 cookie 过期,否则会造成带宽的浪费。

cookie 优化的建议:

减小 cookie 的大小,切记不要在路由根节点设置 cookie,因为这将造成该路径下的全部请求都会带上这些 cookie,同时,静态文件的业务不关心状态,因此,cookie 在静态文件服务下,是没有用处,请不要为静态服务设置 cookie。
为静态组件使用不同的域名,cookie 作用于相同的路由,因此,设定不同的域名,可以防止 cookie 被上传。
减少 dns 查询,这个可以基于浏览器的 dns 缓存来削弱这个副作用的影响 (换用额外域名需要 DNS 查询)。
cookie 的不安全性
cookie 可以在浏览器端,通过调用 document.cookie 来请求 cookie 并修改,修改之后,后续的网络请求中就会携带上修改过后的值。
例如:第三方广告或者统计脚本,将 cookie 和当前页面绑定,这样可以标识用户,得到用户浏览行为。

2.session

cookie 存在各种问题,例如体积大、不安全,为了解决 cookie 的这些问题,session 应运而生,session 只保存在服务器端,客户端无法修改,因此,安全性和数据传递都被保护。

如何将每个客户和服务器中的数据一一对应:

  • 基于 cookie 来实现用户和数据的映射
  • 通过查询字符串来实现浏览器端和服务器端数据的对应。它的原理是检查请求的查询字符串,如果没值,会先生成新的带值的 URL。

安全性
session 的口令保存在浏览器(基于 cookie 或者查询字符串的形式都是将口令保存于浏览器),因此,会存在 session 口令被盗用的情况。当 web 应用的用户十分多,自行设计的随机算法的口令值就有理论机会命中有效的口令值。一旦口令被伪造,服务器端的数据也可能间接被利用,这里提到的 session 的安全,就主要指如何让这一口令更加安全。

有一种方法是将这个口令通过私钥加密进行签名,使得伪造的成本较高。客户端尽管可以伪造口令值,但是由于不知道私钥值,签名信息很难伪造。如此,我们只要在响应时将口令和签名进行对比,如果签名非法,我们将服务器端的数据立即过期即可,

将口令进行签名是一个很好的解决方案,但是如果攻击者通过某种方式获取了一个真实的口令和签名,他就能实现身份的伪造了,一种方案是将客户端的某些独有信息与口令作为原值,然后签名,这样攻击者一旦不在原始的客户端上进行访问,就会导致签名失败。这些独有信息包括用户 IP 和用户代理。

三、单线程原理

退出移动版