关于内存管理:一文揭秘高效稳定的-Apache-Doris-内存管理机制

作者:SelectDB 高级研发工程师、Apache Doris Committer 邹新一背景Apache Doris 作为基于 MPP 架构的 OLAP 数据库,数据从磁盘加载到内存后,会在算子间流式传递并计算,在内存中存储计算的两头后果,这种形式缩小了频繁的磁盘 I/O 操作,充分利用多机多核的并行计算能力,可在性能上出现微小劣势。 在面临内存资源耗费微小的简单计算和大规模作业时,无效的内存调配 、统计、 管控对于零碎的稳定性起着非常要害的作用——更快的内存调配速度将无效晋升查问性能,通过对内存的调配、跟踪与限度能够保障不存在内存热点,及时精确地响应内存不足并尽可能躲避 OOM 和查问失败,这一系列机制都将显著进步零碎稳定性;更准确的内存统计,也是大查问落盘的根底。 问题和思考在内存短缺时内存治理通常对用户是无感的,但实在场景中往往面临着各式各样的极其 Case ,这些都将为内存性能和稳定性带来挑战,在过来版本中,用户在应用 Apache Doris 时在内存治理方面遭逢了以下挑战:OOM 导致 BE 过程解体。内存不足时,用户能够承受执行性能稍慢一些,或是让后到的工作排队,或是让大量工作失败,总之心愿无效限度过程的内存应用而不是宕机;BE 过程占用内存较高。用户反馈 BE 过程占用了较多内存,但很难找到对应内存耗费大的查问或导入工作,也无奈无效限度单个查问的内存应用;用户很难正当的设置每个query的内存大小,所以经常出现内存还很短缺,然而query 被cancel了;高并发性能降落重大,也无奈疾速定位到内存热点;构建 HashTable 的两头数据不反对落盘,两个大表的 Join 因为内存超限无奈实现。针对开发者而言又存在另外一些问题,比方内存数据结构性能重叠且应用凌乱,MemTracker 的构造难以了解且手动统计易出错等。 针对以上问题,咱们经验了过来多个版本的迭代与优化。从 Apache Doris 1.1.0 版本开始,咱们逐步对立内存数据结构、重构 MemTracker、开始反对查问内存软限,并引入过程内存超限后的 GC 机制,同时优化了高并发的查问性能等。在 Apache Doris 1.2.4 版本中,Apache Doris 内存管理机制已趋于欠缺,在 Benchmark、压力测试和实在用户业务场景的反馈中,根本打消了内存热点以及 OOM 导致 BE 宕机的问题,同时可定位内存 Top 的查问、反对查问内存灵便限度。而在最新的 Doris 2.0 alpha 版本中,咱们实现了查问的异样平安,并将逐渐配合 Pipeline 执行引擎和两头数据落盘 , 让用户不再受内存不足困扰。 在此咱们将零碎介绍 Apache Doris 在内存治理局部的实现与优化。 ...

May 25, 2023 · 4 min · jiezi

关于内存管理:记一次-rr-和硬件断点解决内存踩踏问题

在日常的调试过程中,咱们总会遇到一些乏味的 bug,在本文我就遇到了一个有意思的查问后果不统一问题。 故事的开始咱们在测试 NebulaGraph 的 MATCH 语句的时候发现一个很神奇的事件: (root@nebula) [gdlancer]> match (v1)-[e*1..1]->(v2) where id(v1) in [1, 2, 3, 4] and (v2)-[e*1..1]->(v1) return e;+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+| e |+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+| [[:Rel_5 2->2 @0 {Rel_5_0_Bool: true, Rel_5_1_Bool: true, Rel_5_2_Double: 0.533698, Rel_5_3_String: "Stephen Curry", Rel_5_4_Double: 0.162998}]] || [[:Rel_1 2->2 @0 {Rel_1_0_Int: 3, Rel_1_1_Int: 5, Rel_1_2_Int: 81, Rel_1_3_Double: 0.975062, Rel_1_4_Bool: true, Rel_1_5_Int: 59}]] || [[:Rel_0 2->2 @0 {Rel_0_0_Bool: true, Rel_0_1_String: "Kevin Durant", Rel_0_2_String: "Joel Embiid", Rel_0_3_Int: 96, Rel_0_4_Double: 0.468568, Rel_0_5_Int: 98, Rel_0_6_Int: 77}]] || [[:Rel_2 2->2 @0 {Rel_2_0_Int: 38, Rel_2_1_Double: 0.120953, Rel_2_2_String: "Null1", Rel_2_3_Bool: false, Rel_2_4_Bool: true, Rel_2_5_Int: 6, Rel_2_6_String: "Tracy McGrady"}]] || [[:Rel_3 2->2 @0 {Rel_3_0_String: "Aron Baynes", Rel_3_1_String: "LeBron James", Rel_3_2_Double: 0.831096, Rel_3_3_Int: 11}]] || [[:Rel_4 2->2 @0 {Rel_4_0_Bool: true, Rel_4_1_String: "Kevin Durant", Rel_4_2_Double: 0.71757, Rel_4_3_String: "Marc Gasol", Rel_4_4_Double: 0.285247, Rel_4_5_String: "Cory Joseph"}]] |+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+Got 6 rows (time spent 146.7ms/168.31625ms)Tue, 03 Jan 2023 14:10:03 CST(root@nebula) [gdlancer]> match (v1)-[e*1..1]->(v2) where id(v1) in [1, 2, 3, 4] and (v2)-[e*1..1]->(v1) return e;+---+| e |+---++---+Empty set (time spent 30.67ms/58.220042ms)Tue, 03 Jan 2023 14:10:05 CST同样的语句,两次查问的后果集竟然不一样! ...

March 22, 2023 · 33 min · jiezi

关于内存管理:golang的内存管理

0.1、索引https://blog.waterflow.link/articles/1663406367769 1、内存治理内存治理是治理计算机内存的过程,在主存和磁盘之间挪动过程以进步零碎的整体性能。内存治理的根本要求是提供办法来依据程序的申请动静的将局部内存调配给程序,并在不须要时开释它以供重用。 程序通过将他们的内存划分为执行特定工作的不同局部来治理他们。栈和堆就是这部分中的俩个,他们管理程序的未应用的内存并将其调配给不同类型的数据。当程序不再须要这些内存的时候就会开释他们,供后续应用。 2、经典的内存模型 正在运行的程序将其数据保留在这些逻辑区域之中。操作系统在将逻辑加载到内存时为全局变量和动态变量分配内存,并且在程序完结之前不会开释它。这些值不会被批改。另外俩个区域,堆和栈,更多的是动态分配的变量。在这些区域中,程序依据须要去调配和开释内存。这两个区域之间的区别上面会说到。 文本段文本段是指标文件或内存中程序的一部分,其中蕴含可执行指令。文本段个别放在堆或栈的下方,以避免堆栈溢出被笼罩。 初始化数据和未初始化数据段数据段是程序虚拟地址空间的一部分,其中蕴含由程序员初始化的全局变量和动态变量。 栈栈用于动态内存调配,就像数据结构中的栈,遵循后进先出。通常函数和局部变量会在栈上调配,当数据被调配到栈上或是从栈上弹出时,实际上没有任何物理挪动,只有保留在栈中的值会被批改。这使得从栈中存储和查问数据的过程十分快,因为不须要查找。 咱们能够从它最下面的块中存储和查问数据。 存储在栈上的任何数据都必须是无限且动态的。 这意味着数据的大小在编译时是已知的。 堆的内存治理简单明了,由操作系统实现。 因为栈的大小是无限的,咱们可能会在这里遇到堆栈溢出谬误。 堆这里的堆和数据结构中的堆是没有关系的。堆用于动态内存调配。栈只容许在顶部进行调配和开释,而程序能够在堆中的任何地位调配或开释内存。程序必须以与其调配相同的程序将内存返回到栈。然而程序能够以任何程序将内存返回到堆中。这意味着堆比栈更灵便。指针、数组和大数据结构通常存储在堆中。 存储在堆上的数据必须造成一个足够大的间断块,以应用单个内存块满足申请。此属性减少了堆的复杂性。首先,执行调配操作的代码必须扫描堆,直到找到足够大的间断内存块来满足申请。其次,当内存返回堆时,必须合并相邻的已开释块,以更好地适应将来对大块内存的申请。这种减少的复杂性意味着应用堆治理内存比应用堆栈慢。 堆内存调配计划不提供主动解除调配。咱们须要应用垃圾回收器来删除未应用的对象,以便无效地应用内存。 栈和堆的区别与栈相比,堆更慢,因为查找数据的过程波及更多。堆比栈能够存储更多的数据。堆以动静大小存储数据;栈以动态大小存储数据。堆在应用程序的线程之间共享。堆因为其动静个性而更难治理。当咱们议论内存治理时,咱们次要是在议论治理堆内存。堆内存调配不是线程平安的,因为存储在此空间中的数据对所有线程都是可拜访或可见的。内存调配的重要性内存是无限的。如果一个程序持续耗费内存而不开释它,它将耗尽内存并自行解体。因而,软件程序不能得心应手地持续应用内存,它会导致其余程序和过程耗尽内存。因为这一点的重要性,大多数编程语言(包含 Go)都提供了主动内存治理的办法。 3、go的内存模型Go 反对主动内存治理,例如主动内存调配和主动垃圾回收,防止了很多暗藏的 bug。 在 Go 中,每个线程都有本人的堆栈。当咱们启动一个线程时,咱们调配一块内存用作该线程的堆栈。当这块内存不够用时,问题就来了。为了克服这个问题,咱们能够减少堆栈的默认大小,但减少堆栈的大小意味着每个线程的大小都会减少。如果咱们想应用大量线程,这将十分低效。 另一种抉择是独自决定每个线程的堆栈大小。同样,在咱们有很多线程的设置中,这将是低效的,因为咱们须要弄清楚咱们应该为每个堆栈调配多少内存。 Go 的创建者想出的不是给每个 goroutine 一个固定数量的堆栈内存,而是 Go 运行时尝试依据须要为 goroutine 提供所需的堆栈空间。这样咱们在创立线程时就不须要思考堆栈大小了。 goroutine 以2 kb的堆栈大小开始,能够依据须要增长和放大。Go 查看它行将执行的函数所需的堆栈数量是否可用,如果不够用,则调用morestack调配一个新帧,而后才执行该函数。当该函数退出时,它的返回参数被复制回原始堆栈帧,并且任何不须要的堆栈空间都被开释。 堆栈也有下限。如果达到此限度,咱们的应用程序将panic并停止。 Go 在两个中央分配内存:一个用于动态分配的全局堆和一个用于每个 goroutine 的本地堆栈。Go 与许多垃圾收集语言相比的一个次要区别是,许多对象间接调配在程序堆上。Go 更喜爱在栈上调配。栈调配代价更低,因为它只须要两条 CPU 指令:一条推入栈进行调配,另一条从栈中开释。 可怜的是,并非所有数据都能够应用栈上调配的内存。栈调配要求能够在编译时确定变量的生命周期和内存占用。如果无奈确定,则在运行时动态分配到堆上。 Go 编译器应用一个称为逃逸剖析的过程来查找其生命周期在编译时已知的对象,并将它们调配到栈上而不是在垃圾回收的堆内存中。根本思维是在编译时做垃圾回收的工作。编译器跨代码区域跟踪变量的范畴。它应用这些数据来确定哪些变量持有一组查看,以证实它们的生命周期在运行时是齐全可知的。如果变量通过了这些查看,则能够在栈上调配值。如果不是,就代表逃逸,并且必须进行堆调配。 内存是在栈上调配还是逃到堆上齐全取决于你如何应用内存,而不是你如何申明变量。 能够通过上面的命令查看是否有内存逃逸,go build -gcflags '-m'。 4、垃圾回收垃圾回收是主动内存治理的一种模式。垃圾回收器尝试回收由程序调配但不再被援用的内存。 Go 的垃圾回收器是一个非分代并发、三色标记和清理垃圾回收器。 分代垃圾回收器专一于最近调配的对象,因为它假如像长期变量这样的短期对象最常被回收。 Go 编译器更喜爱在栈上调配对象,短期对象通常调配在栈上而不是堆上;这意味着不须要分代GC。 Go 的垃圾回收分为两个阶段,标记阶段和革除阶段。GC 应用三色算法来剖析内存块的应用状况。该算法首先将仍被援用的对象标记为“沉闷”,并在下一阶段(扫描)开释不沉闷对象的内存。 不必回收垃圾,然而能够缩小垃圾导致垃圾回收代价高次要因素之一是堆上的对象数量。通过优化咱们的代码以缩小堆上长寿命对象的数量,咱们能够最大限度地缩小破费在 GC 上的资源,并进步咱们的零碎性能。 重构构造 在读取数据时,古代计算机 CPU 的外部数据寄存器能够保留和解决 64 位。这称为字长。它通常是 32 位或 64 位的。 ...

September 17, 2022 · 2 min · jiezi

关于内存管理:Enhanced-SWAP内存管理-OpenHarmony构建新的内存管理优化方案ESWAP

OpenHarmony是面向全场景泛终端设备的操作系统,终端设备内存性能的强弱会间接影响用户的体验。终端设备的内存差别很大,对于内存比拟小的终端设备,内存优化计划无疑是加强内存性能、晋升用户体验的要害。针对传统内存计划及管理机制的有余,OpenHarmony构建了一套欠缺的内存解决方案——ESWAP。 1. 传统内存计划及管理机制在传统的Linux内存优化计划中,终端设备通常采纳SWAP及ZRAM内存计划。 1.1 SWAP SWAP即内存替换技术或虚拟内存技术,如图1所示,在零碎的物理内存不足时,把内存中的一部分不罕用的内存空间释放出来,以增大零碎可用内存供以后运行的程序应用。这些被开释的数据被长期保留到SWAP分区中,等到须要应用时,再从SWAP分区中复原到内存中。 图1 SWAP虚拟内存技术 从图1中不难看出,SWAP内存替换技术增大了设施内用内存,然而,SWAP内存换入/换出时会遭逢IO性能瓶颈,重大时甚至会影响用户的应用体验,并且flash存储器件的频繁读写也会缩减其寿命。 1.2 ZRAM ZRAM即内存压缩技术,如图2所示,在零碎的物理内存不足时,将零碎物理内存的一部分划分进去作为ZRAM分区,而后把不罕用的匿名页压缩后放到ZRAM分区里,相当于就义了一些CPU效率,以增大零碎可用内存供以后运行的程序应用。等到须要应用时,再从ZRAM分区中将数据解压进去。 图2 ZRAM内存压缩技术 尽管ZRAM在肯定水平上增大了设施内用内存,然而如果没有适合的形式来对内存进行治理,负面影响也会非常明显,将会造成内存页频繁的压缩/解压缩,从而抢占失常业务的CPU工夫,减少零碎的功耗。并且,如果压缩/解压速度不够快的话,会间接影响用户的应用体验。 1.3 内存管理机制 除了内存计划有余,传统的内存调配及治理形式,无奈感知业务个性及数据的重要性。如果终端设备多个过程或业务共用一块内存,当内存负载越来越重,进行内存数据回收时,会频繁呈现数据搬移,以及内存震荡的景象。这些景象会减轻内核治理内存的开销,并导致系统CPU负载长期处于高负载的状态,从而减少零碎功耗。 2. OpenHarmony内存解决方案针对原有内存计划的有余,OpenHarmony构建了一套欠缺的内存解决方案ESWAP,买通了下层零碎到内核的调用栈,让内核能在下层配置的领导下,对每一块内存数据进行正当的治理。 上面咱们将为大家介绍ESWAP解决方案以及其关键技术的解析。 2.1 ESWAP计划介绍 ESWAP(Enhanced SWAP)是OpenHarmony针对内存优化问题提供的一套欠缺的内存解决方案,联合内存压缩和内存替换技术,定制了一套正当高效的调度管理策略,使压缩和替换两者的工作可能高效且均衡。ESWAP基于关联性的数据聚合技术及下层领导策略,将内存划分为不同的分组进行治理,通过回收优先级来辨别不同分组下内存的沉闷水平,优先压缩、换出较不沉闷的内存数据,以晋升数据交换性能,缩小寿命冲击。 ESWAP解决方案的整体框架如图3所示: 图3 ESWAP解决方案 ESWAP解决方案在全局资源调度子系统中减少了一个系统资源调度模块,通过向账户子系统订阅本地账户的变动来感知以后的账户状态和内存状态,而后依据账户状态给各个账户设置不同的回收优先级、设置指标可用内存量、设置压缩和换出的比例等参数,并将这些参数下发给ZSWAPD。ZSWAPD会根据回收优先级判断回收的先后顺序;根据指标可用内存量和以后可用内存量的差值决定回收的量;根据压缩和换出的比例来决定压缩和换出的量,从而实现在达成内存扩大成果前提下的性能和功耗均衡。 2.2 关键技术解析 ESWAP内存解决方案都用到了哪些关键技术呢?上面为你一一道来。 2.2.1 定制的ZRAM和替换分区 ESWAP联合内存压缩和内存替换技术,提供了自定义新增存储分区作为内存替换分区的能力,并在内核中创立了一个常驻过程ZSWAPD,用于将ZRAM压缩后的匿名页加密换出到ESWAP存储分区中,从而能齐全地空出一块可用内存,以此来达到维持Memavailable水线的指标,如图4所示。 图4 ESWAP技术 同时,ESWAP模块还能够记录每个匿名页的冷热特色信息,并将这些数据通过关联性、冷热程序进行相应的寄存,使ESWAP替换区中间断寄存的匿名页具备工夫和空间局部性。因而在匿名页换入时,能够将替换区中的相邻匿名页一并读入ZRAM,以此来保证数据的存取速度,晋升IO性能。 2.2.2 动静的内存回收机制 OpenHarmony提供了一种额定的内存回收机制ZSWAPD,并创立了“buffer”来作为掂量以后零碎内存能力的指标。buffer指的是以后零碎能提供的最大可用内存。ZSWAPD会依据buffer量以及上文所述的各种策略,来对匿名页进行压缩换出以回收。同时,ZSWAPD还能依据内存冷热拆散的合理性以及内存回收状态, 动静地管制ZRAM和ESWAP之间的均衡,从而取得更高的能效比。 2.2.3 灵便的内存回收策略 OpenHarmony基于Memcg分组进行了回收策略的加强,应用回收优先级来领导ZSWAPD回收的先后顺序。回收策略将既定的buffer相干配置下发给ZSWAPD,来领导其回收适当数量的内存。此外,因为匿名页可能存储在RAM、ZRAM、ESWAP三个模块中, 下层能够依据须要,通过灵便地配置替换策略,管制这三个模块中存储的比例,防止频繁换入换出带来的负面影响。 至此,ESWAP的三项关键技术就介绍完了,咱们来总结一下: ● 定制的ZRAM和替换分区:将数据通过关联性、冷热程序进行寄存,保障了数据的存取速度,晋升了IO性能。 ● 动静的内存回收机制:从回收优先级、可用内存量、压缩和替换比例三个维度动静地控制数据回收,从而取得更高的能效比。 ● 灵便的内存回收策略:灵便地管制RAM、ZRAM、ESWAP三个模块数据存储的比例,保障了各个模块的均衡。 2.3 ESWAP相干接口 ESWAP解决方案支持系统开发者定制本人的回收策略,并在/dev/memcg下提供了仅对下层回收策略可见的接口。零碎开发者能够通过这些接口来定制本人的下层策略,具体接口如下所示: 以上就是本文全部内容,ESWAP解决方案仍在一直建设中,期待宽广开发者退出咱们,独特见证全场景智能时代的有限可能! 感兴趣的小伙伴能够通过上面链接获取ESWAP源码进行深刻理解: https://gitee.com/openharmony...

April 27, 2022 · 1 min · jiezi

关于内存管理:Linux内核内存管理总结

微信公众号:奔跑吧linux社区 加奔跑吧微信群请先加微信:runninglinuxkernel欢送订阅奔跑吧配套旗舰篇视频节目:https://weidian.com/?userid=1... 这篇对《奔跑吧Linux内核》第一版的内存治理 做了很棒的笔记和总结。《奔跑吧Linux内核》第一版各大书店有售。这篇文章作者是:Arnold Lu@南京原文:https://www.cnblogs.com/arnol... Linux的内存治理波及到的内容十分庞杂,而且与内核的方方面面耦合在一起,想要了解透彻十分艰难。在开始学习之前进行了一些筹备工作《如何开展Linux Memory Management学习?》, 参考资料遂决定以如下材料作为参考,进行Linux内存治理的钻研:《奔跑吧 Linux内核》:以第2章为底本开展,这是目前能获取的紧跟以后内核倒退(Linux 4.0),并且讲的比拟全面的一本材料。 《Understanding the Linux Virtual Memory Manager》:简略说就是虽老但经典,基于(Linux 2.4/2.6)。作者是目前依然沉闷在Linux社区MM专家。 《wowotech Memory Management》:没有其余系列经典,也没有条理系列的介绍MM,然而依然值得按考。 《tolimit Linux内存源码剖析》:绝对零散的介绍了内存相干剖析文档 《Linux Kernel v4.0》:当然必不可少的,是源码了。 当逐步深刻看到MMU相干代码时,读一下ARM架构对于MMU的规格书有助于了解。 不然对于虚拟地址到物理地址的映射就会很虚无,这些材料包含《ARM Architecture Reference Manual ARMv7-A and ARMv7-R edition》的《Virtual Memory System Architecture》,以及相干MMU TRM。 Linux Memory Management框架图整个内存治理从宏观上能够分为三大部分:用户空间、内核空间和相干硬件。用户空间次要是libc对相干零碎调用进行封装,对用户程序提供API,罕用的有malloc、mmap、munmap、remap、madvise、mempolicy等等。 相干硬件包含MMU/TLB、L1/L2 Cache以及DDR RAM,具体到ARM架构须要对照MMU/L2 Cache以及RAM规格书。 内核空间就简单多了,首先介绍初始化及初始化后的布局。 2.1 物理内存初始化从获取内存大小、初始化页表,再进行zone初始化,而后在zone中应用搭档零碎进行物理内存初始化; 2.2 页表的映射过程讲述了ARM32和ARM64两种架构下的页表映射,如何从虚拟地址由MMU转化成物理页面地址的; 2.3 内核内存的布局图在内存被初始化之后,内核的内存布局基本上就确定了,ARM32和ARM64下布局有很大区别。在malloc一节brk中介绍了用户空间的布局。2.1~2.3是内存的一个动态状态,在有了这些根底之后,2.4~2.9依照从低层到下层的一一介绍了。2.4 调配物理页面介绍了基于搭档零碎的页调配和开释;2.5 slab分配器基于搭档零碎,slab调配更小内存块;以及基于slab的kmalloc;2.6 vmalloc和kmalloc区别在于v,即在VMALLOC区域调配;2.7 VMA即Virtual Memory Area,是过程内存治理的外围;2.8 malloc和2.9 mmap都基于VMA,malloc/free用于调配/开释一块内存;mmap/munmap用于匿名/文件映射到用户空间。以及mmap(补充)。因为malloc/mmap分配内存并不是立刻调配,只是在用到的时候才会触发2.10 缺页中断解决。在缺页但页有余的状况下,就须要进行一些操作调整内存,这些操作的根底是2.11 page援用计数,还有页面的2.12 反向映射RMAP技术。在内存不足状况下触发kswapd2.13 回收页面,其中匿名页面有着非凡的2.14 匿名页面生命周期。在kswapd回收仍然无奈满足内存调配,就须要对内存进行2.16 内存规整,它依赖的技术是2.15 页面迁徙。因为内存中存在一些内容齐全一样的页面,应用2.17 KSM技术进行合并,同时利用COW技术,在须要时重新分配。还介绍了2.18 Dirty COW内存破绽,而后对内存治理数据结构和API进行了总结2.19 总结内存治理数据结构和API。最初2.20 最新更新和瞻望对新技术进行了介绍。除了以上技术,还有如下内存技术: ...

April 3, 2022 · 2 min · jiezi

关于内存管理:如何定位内存泄露

残缺高频题库仓库地址:https://github.com/hzfe/awesome-interview 残缺高频题库浏览地址:https://febook.hzfe.org/ 相干问题垃圾回收机制答复关键点垃圾回收 DevTools 内存透露是指不再应用的内存,没有被垃圾回收机制回收。当内存透露很大或足够频繁时,用户会有所感知:轻则影响利用性能,体现为缓慢卡顿;重则导致利用奔溃,体现为无奈失常应用。为了防止内存透露带来的不良影响,须要对垃圾回收机制进行理解,把握内存透露分析方法,欠缺线上相干监控措施。 内存透露定位和剖析个别须要辅助工具,比方 Chrome DevTools。开发者能够通过 DevTools 记录页面流动详情,生成可视化剖析后果,从时间轴中直观理解内存透露状况;利用 DevTools 获取若干次内存快照,查看内存堆栈变动;以及应用 Chrome 工作管理器,实时监控内存的应用状况。 知识点深刻1. 排查内存透露常见问题在 JavaScript 中,当一些不再须要的数据依然可达时,V8 会认为这些数据仍在被应用,不会开释内存。为了调试内存透露,咱们须要找到被谬误保留的数据,并确保 V8 可能将其清理掉。 代码量较小时,开发者通常能够基于以下根本准则进行疾速自查: 是否滥用全局变量,没有手动回收。是否没有正确销毁定时器、闭包。是否没有正确监听事件和销毁事件。除此之外,开发者能够借助内部工具进行内存透露排查。 2. 应用 Chrome DevTools 定位内存透露Performance 关上筹备剖析的页面和 DevTools 的 Performance 面板,勾选 Memory 并开始录制,在模仿用户操作一段时间后完结录制,DevTools 会将这段时间内的页面行为流动进行记录和剖析。 通过生成的后果能够直观查看到内存工夫线,理解内存随工夫的占用变动,如果内存占用曲线成阶梯状始终回升,则可能存在内存透露。按需选取工夫线中的区域片段,查看对应时间段内的流动类型和工夫占用,作为排查和定位内存透露的辅助方法。 Memory 关上筹备剖析的页面和 DevTools 的 Memory 面板,按需生成快照。每个快照的内容是快照时刻,进行一次垃圾回收后,利用中所有可达的对象。 当开发者明确晓得与内存透露关联的用户交互步骤时,能够生成屡次内存快照进行比照,排查出透露的对象:在做用户交互操作之前,进行一次失常内存堆栈信息的快照;在做用户交互操作中或操作完结时,进行内存快照。应用 Comparison 视图或应用 filter 按需查看快照之间的差别。 下面的图中应用 filter 查看快照 2 和快照 3 的差别,通过后果可知在两个快照之间继续被调配 clickCallback 闭包。通过点击文件门路能够定位到内存透露的代码。 3. Node.js 中的内存透露定位如果须要定位 Node.js 中的内存透露,启动 Node.js 时带上 --inspect 参数,以便利用 Chrome DevTools 工具生成 Memory 快照数据。如图所示,启动 Node.js 服务后,关上 Chrome DevTools,会有 Node 标识,点击能够关上 Node 专用 DevTools。 ...

October 30, 2021 · 1 min · jiezi

关于内存管理:虚拟内存精粹

博客原文虚拟内存精粹 导言虚拟内存是当今计算机系统中最重要的抽象概念之一,它的提出是为了更加无效地治理内存并且升高内存出错的概率。虚拟内存影响着计算机的方方面面,包含硬件设计、文件系统、共享对象和过程/线程调度等等,每一个致力于编写高效且出错概率低的程序的程序员都应该深刻学习虚拟内存。 本文全面而深刻地分析了虚拟内存的工作原理,帮忙读者疾速而粗浅地了解这个重要的概念。 计算机存储器存储器是计算机的核心部件之一,在齐全现实的状态下,存储器应该要同时具备以下三种个性: 速度足够快:存储器的存取速度该当快于 CPU 执行一条指令,这样 CPU 的效率才不会受限于存储器容量足够大:容量可能存储计算机所需的全副数据价格足够便宜:价格低廉,所有类型的计算机都能装备然而事实往往是残暴的,咱们目前的计算机技术无奈同时满足上述的三个条件,于是古代计算机的存储器设计采纳了一种分档次的构造: 从顶至底,古代计算机里的存储器类型别离有:寄存器、高速缓存、主存和磁盘,这些存储器的速度逐级递加而容量逐级递增。存取速度最快的是寄存器,因为寄存器的制作资料和 CPU 是雷同的,所以速度和 CPU 一样快,CPU 拜访寄存器是没有时延的,然而因为价格昂贵,因而容量也极小,个别 32 位的 CPU 装备的寄存器容量是 32✖️32 Bit,64 位的 CPU 则是 64✖️64 Bit,不论是 32 位还是 64 位,寄存器容量都小于 1 KB,且寄存器也必须通过软件自行治理。 第二层是高速缓存,也即咱们平时理解的 CPU 高速缓存 L1、L2、L3,个别 L1 是每个 CPU 独享,L3 是全副 CPU 共享,而 L2 则依据不同的架构设计会被设计成独享或者共享两种模式之一,比方 Intel 的多核芯片采纳的是共享 L2 模式而 AMD 的多核芯片则采纳的是独享 L2 模式。 第三层则是主存,也即主内存,通常称作随机拜访存储器(Random Access Memory, RAM)。是与 CPU 间接替换数据的外部存储器。它能够随时读写(刷新时除外),而且速度很快,通常作为操作系统或其余正在运行中的程序的长期材料存储介质。 最初则是磁盘,磁盘和主存相比,每个二进制位的成本低了两个数量级,因而容量比之会大得多,动辄上 GB、TB,而毛病则是访问速度则比主存慢了大略三个数量级。机械硬盘速度慢次要是因为机械臂须要一直在金属盘片之间挪动,期待磁盘扇区旋转至磁头之下,而后能力进行读写操作,因而效率很低。 主存物理内存咱们平时始终提及的物理内存就是上文中对应的第三种计算机存储器,RAM 主存,它在计算机中以内存条的模式存在,嵌在主板的内存槽上,用来加载各式各样的程序与数据以供 CPU 间接运行和应用。 虚拟内存在计算机领域有一句如同摩西十诫般神圣的哲言:"计算机科学畛域的任何问题都能够通过减少一个间接的中间层来解决",从内存治理、网络模型、并发调度甚至是硬件架构,都能看到这句哲言在闪烁着光辉,而虚拟内存则是这一哲言的完满实际之一。 虚拟内存是古代计算机中的一个十分重要的存储器形象,次要是用来解决应用程序日益增长的内存应用需要:古代物理内存的容量增长曾经十分疾速了,然而还是跟不上应用程序对主存需要的增长速度,对于应用程序来说内存还是可能会不够用,因而便须要一种办法来解决这两者之间的容量差矛盾。为了更高效地治理内存并尽可能打消程序谬误,古代计算机系统对物理主存 RAM 进行形象,实现了虚拟内存 (Virtual Memory, VM) 技术。 ...

April 19, 2021 · 3 min · jiezi

关于内存管理:简单聊聊内存逃逸-|-剑指-offer-golang

问题简略讲讲golang的内存逃逸吗? 解析什么是内存逃逸在程序中,每个函数块都会有本人的内存区域用来存本人的局部变量(内存占用少)、返回地址、返回值之类的数据,这一块内存区域有特定的构造和寻址形式,寻址起来十分迅速,开销很少。这一块内存地址称为栈。栈是线程级别的,大小在创立的时候曾经确定,当变量太大的时候,会"逃逸"到堆上,这种景象称为内存逃逸。简略来说,局部变量通过堆调配和回收,就叫内存逃逸。 内存逃逸的危害堆是一块没有特定构造,也没有固定大小的内存区域,能够依据须要进行调整。全局变量,内存占用较大的局部变量,函数调用完结后不能立即回收的局部变量都会存在堆外面。变量在堆上的调配和回收都比在栈上开销大的多。对于 go 这种带 GC 的语言来说,会减少 gc 压力,同时也容易造成内存碎片。 如何分析程序是否产生内存逃逸build时增加-gcflags=-m 选项可剖析内存逃逸状况,比方输入./main.go:3:6: moved to heap: x 示意局部变量x逃逸到了堆上。 内存逃逸产生机会向 channel 发送指针数据。因为在编译时,不晓得channel中的数据会被哪个 goroutine 接管,因而编译器没法晓得变量什么时候才会被开释,因而只能放入堆中。package mainfunc main() { ch := make(chan int, 1) x := 5 ch <- x // x不产生逃逸,因为只是复制的值 ch1 := make(chan *int, 1) y := 5 py := &y ch1 <- py // y逃逸,因为y地址传入了chan中,编译时无奈确定什么时候会被接管,所以也无奈在函数返回后回收y}局部变量在函数调用完结后还被其余中央应用,比方函数返回局部变量指针或闭包中援用包外的值。因为变量的生命周期可能会超过函数周期,因而只能放入堆中。package mainfunc Foo () func (){ x := 5 // x产生逃逸,因为在Foo调用实现后,被闭包函数用到,还不能回收,只能放到堆上寄存 return func () { x += 1 }}func main() { inner := Foo() inner()}在 slice 或 map 中存储指针。比方 []*string,其前面的数组可能是在栈上调配的,但其援用的值还是在堆上。package mainfunc main() { var x int x = 10 var ls []*int ls = append(ls, &x) // x产生逃逸,ls存储的是指针,所以ls底层的数组尽管在栈存储,但x自身却是逃逸到堆上}切片扩容后长度太大,导致栈空间有余,逃逸到堆上。package mainfunc main() { s := make([]int, 10000, 10000) for index, _ := range s { s[index] = index }}在 interface 类型上调用办法。 在 interface 类型上调用办法时会把interface变量应用堆调配, 因为办法的真正实现只能在运行时晓得。package maintype foo interface { fooFunc()}type foo1 struct{}func (f1 foo1) fooFunc() {}func main() { var f foo f = foo1{} f.fooFunc() // 调用办法时,f产生逃逸,因为办法是动态分配的}防止内存逃逸的方法对于小型的数据,应用传值而不是传指针,防止内存逃逸。防止应用长度不固定的slice切片,在编译期无奈确定切片长度,只能将切片应用堆调配。interface调用办法会产生内存逃逸,在热点代码片段,审慎应用。写在最初喜爱本文的敌人,欢送关注公众号「会玩 code」,专一大白话分享实用技术。 ...

April 18, 2021 · 1 min · jiezi

关于v8:V8-堆栈空间和垃圾回收机制

微信公众号:[前端一锅煮]一点技术、一点思考。栈空间堆空间新生代内存回收老生代内存回收标记革除、标记整顿、增量标记JavaScript 引擎的内存空间次要分为栈和堆。 V8 的垃圾回收策略次要基于分代式垃圾回收机制。依照对象的存活工夫将内存的垃圾回收进行不同分代,而后别离对不同分代的内存应用最适宜的算法。次要分为新生代和老生代,有标记革除、标记整顿、增量标记等办法。 栈空间栈是长期存储空间,次要存储局部变量和函数调用。 根本类型赋值(Number, Boolean, String, Null, Undefined, Symbol, BigInt),零碎会为新的变量在栈内存中调配一个新值。 援用类型赋值,零碎会为新的变量在栈内存中调配一个值,这个值仅仅是指向同一个对象的援用,和原对象指向的都是堆内存中的同一个对象。 对于函数,解释器创立了”调用栈“来记录函数的调用过程。每调用一个函数,解释器就把该函数增加进调用栈,解释器会为被增加进来的函数创立一个栈帧(用来保留函数的局部变量以及执行语句)并立刻执行。如果正在执行的函数还调用了其余函数,新函数会持续被增加进入调用栈。函数执行实现,对应的栈帧立刻被销毁。 两种查看调用栈的办法应用 console.trace() 向 web 控制台输入一个堆栈跟踪。 浏览器开发者工具进行断点调试。 栈溢出栈尽管很轻量,在应用时创立,应用完结后销毁,然而不是能够有限增长的,被调配的调用栈空间被占满时,就会引起”栈溢出“的谬误。 (function foo() { foo()})()Maximum call stack size exceeded. 为什么根本数据类型存储在栈中,援用数据类型存储在堆中?JavaScript 引擎须要用栈来维护程序执行期间的上下文的状态,如果栈空间大了的话,所有数据都寄存在栈空间外面,会影响到上下文切换的效率,进而影响整个程序的执行效率。 堆空间堆空间存储的数据比较复杂,大抵能够划分为 5 个区域: 新生代内存区(new space):新生代内存区会被划分为两块,别离是 from space 和 to space(具体有什么用下文会说),64位零碎下默认 32MB,32位零碎下默认 16MB,通常新创建的对象会先放入 from 中。老生代内存区(old space):较为长久的保留对象,分为两个区域 old pointer space 和 old data space 别离用来寄存 GC 后还存活的指针信息和数据信息,64位零碎下能应用约 1.4GB,32位零碎下能应用约 0.7GB。大对象区(large object space):这里寄存体积超过其余区大小的对象,次要为了防止大对象的拷贝,应用该空间专门存储大对象。单元区、属性单元区、Map区(Cell space、property cell space、map space):Map 空间寄存对象的 Map 信息也就是暗藏类(Hiden Class)最大限度为 8MB;每个 Map 对象固定大小,为了疾速定位,所以将该空间独自进去。代码区 (code Space):次要寄存代码对象,最大限度为 512MB,也是惟一领有执行权限的内存。新生代内存是长期调配的内存,存活工夫短,老生代内存是常驻内存,存活工夫长。 ...

April 11, 2021 · 1 min · jiezi

关于内存管理:Go看源码必会知识之unsafe包

原文链接:戳这里 前言有看源码的敌人应该会发现,Go规范库中大量应用了unsafe.pointer,要想更好的了解源码实现,就要晓得unsafe.pointer到底是什么?所以明天就与大家来聊一聊unsafe包。什么是unsafe家喻户晓,Go语言被设计成一门强类型的动态语言,那么他的类型就不能扭转了,动态也是意味着类型查看在运行前就做了。所以在Go语言中是不容许两个指针类型进行转换的,应用过C语言的敌人应该晓得这在C语言中是能够实现的,Go中不容许这么应用是处于平安思考,毕竟强制转型会引起各种各样的麻烦,有时这些麻烦很容易被觉察,有时他们却又暗藏极深,难以觉察。大多数读者可能不明确为什么类型转换是不平安的,这里用C语言举一个简略的例子: int main(){ double pi = 3.1415926; double *pv = &pi; void *temp = pd; int *p = temp;}在规范C语言中,任何非void类型的指针都能够和void类型的指针互相指派,也能够通过void类型指针作为中介,实现不同类型的指针间接互相转换。下面示例中,指针pv指向的空间本是一个双精度数据,占8个字节,然而通过转换后,p指向的是一个4字节的int类型。这种产生内存截断的设计缺点会在转换后进行内存拜访是存在安全隐患。我想这就是Go语言被设计成强类型语言的起因之一吧。 尽管类型转换是不平安的,然而在一些非凡场景下,应用了它,能够突破Go的类型和内存平安机制,能够绕过类型零碎低效,进步运行效率。所以Go规范库中提供了一个unsafe包,之所以叫这个名字,就是不举荐大家应用,然而不是不能用,如果你把握的特地好,还是能够实际的。 unsafe 实现原理在应用之前咱们先来看一下unsafe的源码局部,规范库unsafe包中只提供了3种办法,别离是: func Sizeof(x ArbitraryType) uintptrfunc Offsetof(x ArbitraryType) uintptrfunc Alignof(x ArbitraryType) uintptrSizeof(x ArbitrayType)办法次要作用是用返回类型x所占据的字节数,但并不蕴含x所指向的内容的大小,与C语言规范库中的Sizeof()办法性能一样,比方在32位机器上,一个指针返回大小就是4字节。Offsetof(x ArbitraryType)办法次要作用是返回构造体成员在内存中的地位离构造体起始处(构造体的第一个字段的偏移量都是0)的字节数,即偏移量,咱们在正文中看一看到其入参必须是一个构造体,其返回值是一个常量。Alignof(x ArbitratyType)的次要作用是返回一个类型的对齐值,也能够叫做对齐系数或者对齐倍数。对齐值是一个和内存对齐无关的值,正当的内存对齐能够进步内存读写的性能。个别对齐值是2^n,最大不会超过8(受内存对齐影响).获取对齐值还能够应用反射包的函数,也就是说:unsafe.Alignof(x)等价于reflect.TypeOf(x).Align()。对于任意类型的变量x,unsafe.Alignof(x)至多为1。对于struct构造体类型的变量x,计算x每一个字段f的unsafe.Alignof(x,f),unsafe.Alignof(x)等于其中的最大值。对于array数组类型的变量x,unsafe.Alignof(x)等于形成数组的元素类型的对齐倍数。没有任何字段的空struct{}和没有任何元素的array占据的内存空间大小为0,不同大小为0的变量可能指向同一块地址。仔细的敌人会发发现这三个办法返回的都是uintptr类型,这个目标就是能够和unsafe.poniter类型互相转换,因为*T是不能计算偏移量的,也不能进行计算,然而uintptr是能够的,所以能够应用uintptr类型进行计算,这样就能够能够拜访特定的内存了,达到对不同的内存读写的目标。三个办法的入参都是ArbitraryType类型,代表着任意类型的意思,同时还提供了一个Pointer指针类型,即像void *一样的通用型指针。 type ArbitraryType inttype Pointer *ArbitraryType// uintptr 是一个整数类型,它足够大,能够存储type uintptr uintptr下面说了这么多,可能会有点懵,在这里对三种指针类型做一个总结: *T:一般类型指针类型,用于传递对象地址,不能进行指针运算。unsafe.poniter:通用指针类型,用于转换不同类型的指针,不能进行指针运算,不能读取内存存储的值(需转换到某一类型的一般指针)uintptr:用于指针运算,GC不把uintptr当指针,uintptr无奈持有对象。uintptr类型的指标会被回收。三者关系就是:unsafe.Pointer是桥梁,能够让任意类型的指针实现互相转换,也能够将任意类型的指针转换为uintptr进行指针运算,也就说uintptr是用来与unsafe.Pointer打配合,用于指针运算。画个图示意一下: 基本原理就说到这里啦,接下来咱们一起来看看如何应用~ unsafe.Pointer根本应用咱们在上一篇剖析atomic.Value源码时,看到atomic/value.go中定义了一个ifaceWords构造,其中typ和data字段类型就是unsafe.Poniter,这里应用unsafe.Poniter类型的起因是传入的值就是interface{}类型,应用unsafe.Pointer强转成ifaceWords类型,这样能够把类型和值都保留了下来,不便前面的写入类型查看。截取局部代码如下: // ifaceWords is interface{} internal representation.type ifaceWords struct { typ unsafe.Pointer data unsafe.Pointer}// Load returns the value set by the most recent Store.// It returns nil if there has been no call to Store for this Value.func (v *Value) Load() (x interface{}) { vp := (*ifaceWords)(unsafe.Pointer(v)) for { typ := LoadPointer(&vp.typ) // 读取曾经存在值的类型 /** ..... 两头省略 **/ // First store completed. Check type and overwrite data. if typ != xp.typ { //以后类型与要存入的类型做比照 panic("sync/atomic: store of inconsistently typed value into Value") }}下面就是源码中应用unsafe.Pointer的一个例子,有一天当你筹备读源码时,unsafe.pointer的应用到处可见。好啦,接下来咱们写一个简略的例子,看看unsafe.Pointer是如何应用的。 ...

February 1, 2021 · 3 min · jiezi

关于内存管理:前端面试每日-31-第652天

明天的知识点 (2021.01.27) —— 第652天 (我也要出题)[html] 如何在页面关上PDF文件?[css] 你有应用过css的clamp函数吗?说说它有什么用处?[js] 增加原生事件如果不移除为什么会内存泄露?[软技能] 说说你对大数据的了解《论语》,曾子曰:“吾日三省吾身”(我每天屡次检查本人)。前端面试每日3+1题,以面试题来驱动学习,每天提高一点!让致力成为一种习惯,让奋斗成为一种享受!置信 保持 的力量!!!欢送在 Issues 和敌人们一起探讨学习! 我的项目地址:前端面试每日3+1【举荐】欢送跟 jsliang 一起折腾前端,零碎整顿前端常识,目前正在折腾 LeetCode,打算买通算法与数据结构的任督二脉。GitHub 地址 微信公众号欢送大家前来探讨,如果感觉对你的学习有肯定的帮忙,欢送点个Star, 同时欢送微信扫码关注 前端剑解 公众号,并退出 “前端学习每日3+1” 微信群互相交换(点击公众号的菜单:交换)。 学习不打烊,充电加油只为遇到更好的本人,365天无节假日,每天早上5点纯手工公布面试题(死磕本人,愉悦大家)。心愿大家在这虚夸的前端圈里,放弃沉着,保持每天花20分钟来学习与思考。在这变幻无穷,类库层出不穷的前端,倡议大家不要等到找工作时,才狂刷题,提倡每日学习!(不忘初心,html、css、javascript才是基石!)欢送大家到Issues交换,激励PR,感激Star,大家有啥好的倡议能够加我微信一起交换探讨!心愿大家每日去学习与思考,这才达到来这里的目标!!!(不要为了谁而来,要为本人而来!)交换探讨欢送大家前来探讨,如果感觉对你的学习有肯定的帮忙,欢送点个[Star]

January 27, 2021 · 1 min · jiezi