引擎V8推出“并发标记”,可节省60%-70%的GC时间

标记是 V8 Mark-Compact GC 工作的一个阶段。在这个阶段中,收集器发现并标记所有活动对象。标记从一组已知的活动对象开始,如全局对象和激活函数,即所谓的 roots,收集器将 roots 标记为活动的对象,并顺着指针去寻找发现更多的活动对象。收集器继续标记新发现的对象并跟随指针移动,直到没有发现更多的对象要标记为止。在标记结束时,所有无法让应用程序访问的未标记对象,都可以安全地回收。

我们可以将标记视为图遍历(Graph traversal)。堆内存上的对象是下图中的节点,指针从一个对象指向另一个对象是图的边缘。给定图中的一个节点,我们可以使用该对象的隐藏类找到该节点的所有外边缘。

V8 使用每个对象的两个 mark-bits 和一个标记工作表来实现标记。两个 mark-bits 编码三种颜色:白色(00),灰色(10)和黑色(11)。最初所有对象都是白色的,这意味着收集器还没有发现它们。当收集器发现它并将其推到标记工作表上时,白色对象变灰。当收集器将它从标记工作列表中弹出并访问其全部字段时,灰色对象变黑,这种方案被称为三色标记法。当没有灰色对象时,标记结束。所有剩余的白色对象都可以安全地被回收。

请注意,上述标记算法仅适用于在标记进行中应用程序暂停的情况。如果我们允许应用程序在标记过程中运行,那么应用程序可以更改图形并最终诱骗收集器释放活动对象。

减少标记停顿

对大型的堆内存来说,可能需要几百毫秒才能完成一次标记。

长时间的停顿可能会导致应用程序无法响应,并导致用户体验不佳。2011 年,V8 从 stop-the-world 标记切换到增量标志。在增量标记期间,GC 将标记工作分解为更小的模块,并允许应用程序在模块之间运行:

GC 决定每个模块中执行多少增量标记以匹配应用程序的分配速率。一般情况下,这极大地提高了应用程序的响应速度。但对于大型堆内存来说,收集器试图跟上应用程序分配速率的过程中,仍然可能会有长时间的停顿。

再者增量标记并不是免费的,应用程序必须通知 GC 关于更改对象图的所有操作。V8 使用 Dijkstra-style write-barrier 来实现通知,在每次用 JavaScript 写入 object.field = value 之后,V8 插入 write-barrier 代码:

// Called after `object.field = value`.
write_barrier(object, field_offset, value) {
  if (color(object) == black && color(value) == white) {
    set_color(value, grey);
    marking_worklist.push(value);
  }
}

增量标记很好地集成了 GC 的闲置时间(idle time)。Chrome 的 Blink 任务调度程序在主线程的闲置时间内可以调度小增量标记步骤,而且不会造成混乱。如果闲置时间可用,优化效果会非常好。

由于 write-barrier 会有消耗,增量标记可能会降低应用程序的吞吐量。通过使用额外的 worker threads 可以提高吞吐量和暂停时间。有两种方法可以在 worker threads 上进行标记:平行标记(parallel marking)和并发标记(concurrent marking)。

平行标记发生在主线程和工作线程(worker threads)上,应用程序在整个平行标记阶段暂停,它是 stop-the-world 标记的多线程版本。

并发标记主要发生在工作线程上,当并发标记进行时,应用程序可以继续运行。

以下两节将讲述如何在 V8 中添加对平行标记和并行标记的支持。

平行标记

在平行期间,我们可以假定应用程序没有运行。这大大简化了实现过程,因为我们可以假定对象图是静态的并且不会发生变化。为了平行标记对象图,我们需要确保 GC 数据结构是线程安全的,并找到一种方法有效地在线程之间共享标记工作。下图显示了平行标记所涉及的数据结构。箭头指示数据流的方向,为简单起见,该图省略了整理堆内存碎片所需的数据结构。

需要注意的是,线程只能从对象图中读取并且不会被更改。对象的标记位点和标记工作表必须支持读取和写入的访问。

并发标记

当工作线程正访问堆内存上的对象时,并发标记允许 JavaScript 在主线程上运行,这为许多潜在的数据竞争(data races) 打开了大门。例如,当工作线程正在读取字段时,JavaScript 可能正在写入对象字段。数据竞争可能会让 GC 错误地释放活动对象或将原始值与指针混合在一起。

主线程上每个更改对象图的操作都是数据竞争的潜在来源。由于 V8 是一款高性能引擎,具有许多对象布局优化功能,因此潜在的数据竞争来源很多。以下是可能导致的部分结果:

  • 对象分配* 写入一个对象字段* 对象布局更改* 从 snapshot 中反序列化* Materialization during deoptimization of a function.* 在新一代 GC 中疏离(Evacuation)* 代码修补主线程需要与工作线程同步,同步的成本和复杂程度取决于操作。

  Write barrier

写入对象字段导致的数据竞争,可将写入操作调整为 atomic write,并调整 write barrier 来解决:

  保释清单(Bailout worklist)

某些操作(例如代码修补)需要独家访问该对象。早期,我们决定避免对象锁定,因为它们可能导致优先级逆转( priority inversion)问题,在这个过程中,主线程必须等待一个因为持有锁定对象而被取消调度的工作线程。我们不锁定对象,而是允许工作线程访问该对象。工作线程通过将对象推入保释清单来完成该工作,这个过程只能由主线程来处理:

工作线程保释了优化的代码对象、隐藏类和 weak collections,因为访问它们需要锁定或高昂的同步协议。

回顾过去,保释清单对增量开发来说非常有用,我们开始使用工作线程来释放所有对象类型并逐个添加并发标记。

  更改对象布局

对象的字段可以存储三种值:标记的指针、标记的小整数(也称为 Smi),或未标记的值(如拆箱的浮点数)。

通过将对象转换为另一个隐藏类,V8 中将对象字段从标记的状态变为未标记的状态(反之亦然),这种更改对象布局的方式对并发标记来说是不安全的。

如果在工作线程中使用旧的隐藏类访问对象时发生更改,则可能会出现两种类型的错误。首先,worker 可能会错过一个指针,认为这是一个没有标记的值。write barrier 可以防止这种错误。其次,worker 可能会将未标记的值视为指针并放弃引用它,这会导致无效的内存访问,通常会导致程序崩溃。为了处理这种情况,我们使用在对象标记位上同步的 snapshotting 协议。协议涉及两方面:主线程将对象字段从标记变为未标记,然后工作线程访问该对象。在更改字段之前,主线程会确保该对象被标记为黑色,并将其推入保释清单中供以后访问:

atomic_color_transition(object, white, grey);
if (atomic_color_transition(object, grey, black)) {
  // The object will be revisited on the main thread during draining
  // of the bailout worklist.
  bailout_worklist.push(object);
}
unsafe_object_layout_change(object);

如下面的代码片段所示,工作线程首先加载对象的隐藏类,并使用 atomic relaxed 加载操作来快照(snapshots)隐藏类指定对象中的所有指针字段。然后它会尝试使用 atomic compare 和 swap 操作将对象标记为黑色。如果标记成功,则意味着快照必须与隐藏类一致,因为主线程在更改其布局之前会将对象标记为黑色。

snapshot = [];
hidden_class = atomic_relaxed_load(&object.hidden_class);
for (field_offset in pointer_field_offsets(hidden_class)) {
  pointer = atomic_relaxed_load(object + field_offset);
  snapshot.add(field_offset, pointer);
}
if (atomic_color_transition(object, grey, black)) {
  visit_pointers(snapshot);
}

放在一起

我们将并发标记整合到现有的增量标记基础设施中,主线程通过扫描 roots 并填充标记工作表来启动标记。之后,它会在工作线程上发布并发标记任务。工作线程通过合作清空(draining)标记工作表以加快主线程标记进度。主线程偶尔也会通过处理保释清单和标记工作表参与标记。标记工作表变空后,主线程完成 GC。在最终确定之前,主线程重新扫描 roots ,可能会发现更多的白色对象,这些对象在工作线程的帮助下被平行标记。

结果

测试结果显示移动和桌面上每个 GC 周期的主线程标记时间分别减少了 65%和 70%。

最后,我们需要说的是 Node.js v10 现已支持并发标记。

网络安全入门学习路线

其实入门网络安全要学的东西不算多,也就是网络基础+操作系统+中间件+数据库,四个流程下来就差不多了。

1.网络安全法和了解电脑基础

其中包括操作系统Windows基础和Linux基础,标记语言HTML基础和代码JS基础,以及网络基础、数据库基础和虚拟机使用等...

别被这些看上去很多的东西给吓到了,其实都是很简单的基础知识,同学们看完基本上都能掌握。计算机专业的同学都应该接触了解过,这部分可以直接略过。没学过的同学也不要慌,可以去B站搜索相关视频,你搜关键词网络安全工程师会出现很多相关的视频教程,我粗略的看了一下,排名第一的视频就讲的很详细。 当然你也可以看下面这个视频教程仅展示部分截图 学到http和https抓包后能读懂它在说什么就行。

2.网络基础和编程语言

3.入手Web安全

web是对外开放的,自然成了的重点关照对象,有事没事就来入侵一波,你说不管能行吗! 想学好Web安全,咱首先得先弄清web是怎么搭建的,知道它的构造才能精准打击。所以web前端和web后端的知识多少要了解点,然后再学点python,起码得看懂部分代码吧。

最后网站开发知识多少也要了解点,不过别紧张,只是学习基础知识。

等你用几周的时间学完这些,基本上算是具备了入门合格渗透工程师的资格,记得上述的重点要重点关注哦! 再就是,要正式进入web安全领域,得学会web渗透,OWASP TOP 10等常见Web漏洞原理与利用方式需要掌握,像SQL注入/XSS跨站脚本攻击/Webshell木马编写/命令执行等。

这个过程并不枯燥,一边打怪刷级一边成长岂不美哉,每个攻击手段都能让你玩得不亦乐乎,而且总有更猥琐的方法等着你去实践。

学完web渗透还不算完,还得掌握相关系统层面漏洞,像ms17-010永恒之蓝等各种微软ms漏洞,所以要学习后渗透。可能到这里大家已经不知所云了,不过不要紧,等你学会了web渗透再来看会发现很简单。

其实学会了这几步,你就正式从新手小白晋升为入门学员了,真的不算难,你上你也行。

4.安全体系

不过我们这个水平也就算个渗透测试工程师,也就只能做个基础的安全服务,而这个领域还有很多业务,像攻防演练、等保测评、风险评估等,我们的能力根本不够看。

所以想要成为一名合格的网络工程师,想要拿到安全公司的offer,还得再掌握更多的网络安全知识,能力再更上一层楼才行。即便以后进入企业,也需要学习很多新知识,不充实自己的技能就会被淘汰。

从时代发展的角度看,网络安全的知识是学不完的,而且以后要学的会更多,同学们要摆正心态,既然选择入门网络安全,就不能仅仅只是入门程度而已,能力越强机会才越多。

尾言

因为入门学习阶段知识点比较杂,所以我讲得比较笼统,最后联合CSDN整理了一套【282G】网络安全从入门到精通资料包,需要的小伙伴可以点击链接领取哦! 网络安全重磅福利:入门&进阶全套282G学习资源包免费分享!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值