关于垃圾回收机制:Datenlord-垃圾回收机制与无锁化编程二

上一篇文章介绍了无锁化编程场景下的一种垃圾回收机制,Epoch-based Memory Reclaimation(EB)。 本篇介绍另一种无锁化编程场景下的垃圾回收机制,Hazard Pointer(HP)。HP也是一种确定型GC。 HP的内存回收办法比较简单: 对无锁化编程场景下的每个线程,须要显式标注出该线程要竞争拜访的共享对象,即线程把要竞争拜访的对象的指针标注为危险指针(Hazard Pointer), 拜访完结后或勾销标注该危险指针、或标注该危险指针指向的共享对象为待回收。 在回收内存时,HP判断每个待回收的共享对象是否能够平安回收,只须要查看该对象的指针是否正被某个线程标注为危险指针,如果没有就能回收,否则不能回收。 HP跟EB一样也是采纳空间换工夫的策略,并不是马上回收每个能够被回收的共享对象,而是批量回收,以缩小内存回收对程序性能的影响。 确定型GC算法Hazard Pointer(HP)持续沿用无锁化堆栈作为例子来展现HP的用法,而后再介绍HP的细节。 Hazard Pointer(HP)的用法采纳HP作为GC的无锁化堆栈的入栈和出栈操作实现如下所示。 struct Node { void* data; std::atomic< Node * > next;};std::atomic<Node *> top; // 栈顶top.store( nullptr ); // 初始化栈顶为空指针bool push( Node* new_node ) { // 线程本地的危险指针队列里新增一个危险指针 HP * hazard_cur_top = LocalHP::new_hp(); while ( true ) { Node * cur_top = top.load(); // 标注以后栈顶指针cur_top为危险指针 hazard_cur_top.set(cur_top); new_node->next.store( cur_top ); // CAS调用批改栈顶 if ( top.compare_exchange_weak( cur_top, new_node )) { break; // 批改栈顶胜利 } } // 入栈操作胜利,勾销标注cur_top为危险指针 hazard_cur_top.set(nullptr); return true;}Node * pop() { // 线程本地的危险指针队列里新增两个危险指针 HP * hazard_cur_top = LocalHP::new_hp(); HP * hazard_next = LocalHP::new_hp(); while ( true ) { Node * cur_top = top.load(); if ( cur_top == nullptr ) { break; // 堆栈为空 } // 标注以后栈顶指针cur_top为危险指针 hazard_cur_top.set(cur_top); Node * next = cur_top->next.load(); // 标注以后栈顶的下一节点指针next为危险指针 hazard_next.set(cur_top); // CAS调用批改栈顶 if ( top.compare_exchange_weak( cur_top, next )) { break; // 批改栈顶胜利 } } if ( cur_top != nullptr ) { // 出栈操作胜利,标注cur_top为待回收对象 hazard_cur_top.maybe_reclaim(); } else { // 栈为空,勾销标注cur_top为危险指针 hazard_cur_top.set(nullptr); } // 出栈操作完结,勾销标注next为危险指针 hazard_next.set(nullptr); return cur_top;}下面的实现能够看出,入栈操作和出栈操作有不同的危险指针。对于入栈操作: ...

July 21, 2022 · 1 min · jiezi

关于垃圾回收机制:JavaScript垃圾回收机制

前言咱们晓得,JavaScript中的变量次要分为两种类型:根本类型和援用类型。根本类型的值存储在栈(stack)内存中,而援用类型值的存储须要用到栈内存和堆(heap)内存,栈内存保留着变量的堆内存地址,地址指向的堆内存空间保留着具体的值。栈中变量的值在应用完后会被立刻回收,而堆中变量的值不会立刻回收,须要手动回收或应用某种策略进行回收。 JavaScript具备主动垃圾回收机制,不须要像C/C++语言那样须要开发者手动跟踪内存的应用状况。原理很简略:找出那些不再持续应用的变量,而后开释其占用的内存。为此,垃圾收集器会依照固定的工夫距离(或代码执行中预约的收集工夫),周期性地执行这一操作。 那么,怎么判断哪些变量有用哪些变量没用呢?以函数来说,函数中的局部变量只在函数执行过程中存在,在这个过程中,会为局部变量在栈或堆内存上调配相应的空间,以便存储它们的值。而后在函数中应用这些变量,直到函数执行完结。这时,局部变量就没有存在的必要了,因而能够开释它们的内存以供未来应用。这种状况下很容易判断变量是否有还有存在的必要;但并非所有状况下(如闭包)都这么容易就能得出结论。垃圾收集器必须跟踪有用或无用的变量,对不再有用的变量打上标记,以备未来发出其占用的内存。用于标识无用变量的策略可能会因实现而不同,但具体到浏览器中但实现,通常有两个策略:“标记革除”和“援用计数” 注:以上内容摘自《JavaScript高级程序设计(第3版)》 标记革除“标记革除”算法是目前宽泛应用最宽泛的垃圾收集算法,它的策略是,用某种标记办法将有用变量和无用变量进行辨别,做好标记后,下一次垃圾收集器工作的时候,就将标记为“垃圾”的变量进行回收,开释其所占内存。 垃圾收集器如何给变量打上标记呢?要了解它的工作形式,咱们要了解一个JavaScript内存治理的重要的概念:可达性。 可达性所谓“可达性”,是指变量能够由“根”登程,通过一层或多层能够被拜访到。如果一个变量从根登程能够被拜访到,那么它就是“可达”的。垃圾回收器将可达的变量视为有用的变量,将那些不可达的变量视为无用的变量,并给无用的变量打上“垃圾”的标记,便于之后的回收操作。 在JavaScript中,有一组根本的固有可达值,因为不言而喻的起因无奈删除。例如: 本地函数的局部变量和参数以后嵌套调用链上的其余函数的变量和参数全局变量还有一些其余外部的值咱们以一段代码为例: function marry(man,woman){ man.wife = woman woman.husban = man}var man = { name:'Tom'}var woman = { name:'Mary'}var family = marry(man,woman)这段代码在浏览器中运行时,内存示意如下:   能够看到,man、woman、family这三个全局变量都挂在到了window对象上,依据咱们对“可达性”的定义,这些变量的值都能够从window登程被拜访到,它们都是“可达”的,垃圾回收器不会对它们进行回收。 当初,咱们尝试让垃圾回收器回收man指向的对象 man = null这时候的内存图变为 能够看到,此时之前创立的man对象的值仍然能够通过window.woman.husband或window.family.father拜访到,因而这个对象仍被视为有用变量,不会被回收。接下来咱们“切断”所有拜访门路: delete woman.husband delete family.father 此时内存图变为: 此时,因为man对象曾经无奈从“根”登程拜访到了,因而它未来要被垃圾回收器当成“垃圾”回收。 为了加深了解,咱们换一种操作,间接从根开始切断它们的拜访门路: man = null woman = null family = null此时很容易得出内存图示: 如图所见,尽管之前定义的man、woman、family相互之间还有关联(援用),但它们曾经无奈从根登程拜访到了,成为了一座“孤岛”。垃圾回收器在运行的时候会将它们标记为“不可达”变量或“垃圾”,在回收的时候将它们“捡起来”。 这便是“可达性”。 说完了“可达性”,咱们来说说垃圾回收器是如何进行标记的。如上文所述,标记只是一种策略,如何标记要看各浏览器具体的实现。 上面咱们以《JavaScript高级程序设计》中的一段话为例: 垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记(当然,能够应用任何标记形式)。而后,它会去掉环境中的变量以及被环境中的变量援用的变量的标记。而在此之后再被加上标记的变量将被视为筹备删除的变量,起因是环境中的变量曾经无法访问到这些变量来。最初,垃圾收集器实现内存革除工作,销毁那些带标记的值并回收它们所占用的内存空间。 标记革除算法分为两个阶段:标记阶段和革除阶段。 上文援用这段话咱们能够这么了解:在标记阶段,垃圾收集器先给内存中的所有变量都打上一个的标记,接着,从所有的“根”登程,沿着援用链路把沿途的所有变量的标记勾销;而那些无奈被拜访到的变量身上仍带有标记,这些变量就是之后要回收的“垃圾”,咱们将它们标记为“垃圾”;在革除阶段,垃圾回收器将那些被标记为“垃圾”的变量收集起来,在适当的机会将它们销毁,并回收它们占用的内存空间。 以上文彻底删除man为例形容这一过程。 首先,给内存中所有变量打上标记(途中黄色局部): 接着,沿着根登程,将沿途可拜访到的变量的标记去掉: 这时候能够很容易辨认到,之前man指向的对象是“垃圾”,咱们给他打个更显眼的标记: 这样垃圾回收器就很容易地找到这个“垃圾”将它回收了。 ...

February 11, 2021 · 1 min · jiezi

关于垃圾回收机制:我是这样跟面试官讲垃圾回收的

垃圾回收机制是什么?咱们为什么要学习垃圾回收机制?明天咱们就带着这两个问题一起来看看。在咱们日常的开发过程中,并不会过多的关注对象的回收和开释,JVM就能够帮忙咱们来实现垃圾,缩小了咱们很多的工作量,好像垃圾回收离咱们很远,其实垃圾回收机制是咱们从高级到中高级开发必须把握的。把回收对象的工作齐全交给JVM,看似解放了,其实也减少了不确定性,事件并不是什么时候都是完满的,在现如今各种简单业务场景下,不适合的垃圾回收算法及策略,往往是导致咱们零碎性能瓶颈的次要起因。 垃圾回收也不能一概而论,不同的业务场景采取不同的措施,如果业务场景对内存的要求比拟高,就须要进步对象的回收效率,如果是CPU使用率高,这个时候就要升高垃圾回收频率。 咱们都晓得,JVM的内存中有多个区域,垃圾回收次要是看堆和办法区的内存,因为其余区域如程序计数器、虚拟机栈和本地办法栈等区域的内存具备确定性,所以咱们要把眼光次要放在堆中的对象回收和办法区的废除常量的回收。 JVM如何判断一个对象能够回收的?最开始接触垃圾回收的时候,应该都听过,对象没有被援用的时候就能够被回收,然而怎么判断对象是否被援用,次要有两种形式:援用计数算法和可达性剖析算法。 援用计数算法:所谓的援用计数算法,就是通过一个对象的援用计数器来判断该对象是否被援用,对象被援用的时候,计数器就加1,援用生效计数器就减1。计数器的值为0 的时候就阐明这个对象没有被援用了,能够被JVM回收了。须要留神的是,援用计数算法尽管实现形式简略,然而会呈现循环援用的问题。 可达性剖析算法:可达性剖析算法的根底是GC Roots,是所有对象的跟对象,在JVM加载时,会创立一些对象援用失常对象,这些对象作为这些失常对象的起始点,在垃圾回收时,JVM会从GC Roots开始向下搜寻,如果一个对象到GC  Roots没有任何援用链相连时,就证实这个对象能够回收了。 垃圾回收线程是如何回收对象的?JVM去回收对象次要听从两个个性:自动性、不可预期性。 自动性:JVM会创立一个零碎级的线程来跟踪每一块被调配进来的内存,在JVM闲暇时,就会主动的查看每一块调配进来的内存空间,而后主动回收每一块内存。 不可预期性:不可预期性次要是一个对象没有被援用的时候,是立马就被回收的吗,这个答案是未知的,有可能立马就被回收,有可能隔了很久仍然在内存中。 GC算法JVM给咱们提供了多种回收算法来实现回收机制,一般来说,市面上常见的垃圾收集器的回收算法次要分为四类: 标记-革除算法(Mark-Sweep)长处:不须要挪动对象,简略高效 确定:标记-革除的过程效率低,会产生内存碎片。 复制算法(Copying)长处:简略高效,不会产生内存碎片 毛病:内存使用率低,还有可能产生频繁复制的问题。 标记-整顿算法(Mark-Compact)长处:不须要挪动对象,效率高,不产生内存碎片 毛病:须要挪动部分对象 分代收集算法(Gennerational Collection)长处:分区回收 毛病:对于长期存活对象的回收成果不太好。 理解了四种垃圾收集器的回收算法之后,咱们再来看看基于这些算法实现的回收器,简略介绍几种常见的: 掂量GC性能的规范?垃圾收集器各种各样的,不同的场景实用不同的回收器,如何筛选适合的垃圾收集器,次要取决于垃圾收集器的三个指标:吞吐量、卡顿工夫、垃圾回收频率。 吞吐量:指零碎应用程序破费的工夫和零碎运行总时长的比值,GC 的吞吐量=GC耗时/零碎总运行工夫。GC的吞吐量个别不低于95%。 卡顿工夫:卡顿工夫是垃圾收集器在工作的时候,应用程序暂停的工夫。个别串行收集器的卡顿工夫较长,并发收集器的卡顿工夫因为收集器和应用程序交替运行,所以卡顿工夫会比拟短,然而效率不如串行的,零碎吞吐量会有所降落。 垃圾回收频率:垃圾回收频率工夫和卡顿工夫是相互影响的,咱们能够通过增大内存的形式来升高垃圾回收产生的频率,然而内存增大后,沉积的对象就更多,当垃圾回收时,卡顿的工夫就会减少。所以咱们要把握减少内存的这个度,来保障失常的垃圾回收频率即可。 如何查看并剖析GC日志?前边废话这么多,预计很多大兄弟都看烦了,接下来咱们来看看如何收集GC日志,并剖析GC日志,咱们须要JVM参数来设置GC日志,须要关注以下几个参数: -XX:+PrintGC  #输入GC日志-XX:+PrintGCDetails #输入GC的具体日志-XX:+PrintGCTimeStamps #输入GC的工夫戳(以基准工夫的模式)-XX:+PrintGCDateStamps #输入GC的工夫戳(以日期的模式,如 2020-12-08T23:59:59.234+0800)-XX:+PrintHeapAtGC #在进行GC的前后打印出堆的信息-Xloggc:../logs/gc.log #日志文件的输入门路咱们按需配置参数即可,打印后的日志,例如下图:很短时间的GC日志咱们能够用记事本关上去查看,如果是剖析长时间的GC日志,再用记事本关上去看就有点艰难,咱们就须要借助工具来剖析,个别省事的能够用GCViewer来关上日志文件,就能够图形化的查看GC性能。通过工具咱们能够看到吞吐量、卡顿工夫、GC频率,很直观的查看GC的性能状况。 GCeasy也是一个更好用的GC日志剖析工具,只须要把日志文件压缩一下,上传官网就能够在线剖析,下边是我应用一个本地的GC日志剖析的后果: GC调优上边通过剖析GC日志,找出影响性能的问题,接下来就该有针对性的调优了,简略介绍几种罕用的调优策略,次要是升高Minor GC和Full GCd 频率。 升高Minor GC频率咱们首先来看,Minor GC次要是针对Eden区的对象回收,因为新生代空间个别比拟小,Eden区很块就会满,就会导致Minor GC的频率比拟高,咱们的解决办法通常是增大新生代空间来升高Minor GC的频率。在前边讲掂量GC性能指标的时候,咱们提到增大内存会减少回收时候的卡顿工夫。Minor GC也会导致应用程序的卡顿,只是工夫十分短暂,那么扩充Eden区会不会导致Minor GC的工夫增长,还得深刻看一下一次Minor GC产生了什么。 每次Minor GC次要做了两件事,扫描新生代(A)和复制存活对象(B)。其中复制对象的耗时是远高于扫描对象的。咱们举个例子,如果一个对象在Eden区域存活500ms,Minor GC的频率是300ms一次,失常状况下,在一次Minor GC中用时就说A+B的工夫,这个时候咱们通过gc日志剖析,把Eden扩容,变成了600ms才进行一次Minor GC,此时这个对象在Eden区中曾经被回收,就不必复制对象了,就省去了复制存活对象的工夫,在这一次Minor GC中只是减少了扫描新生代的工夫。 总结:单次 Minor GC 工夫更多取决于 GC 后存活对象的数量,而非 Eden 区的大小。如果堆内存中存活工夫比拟长的对象多,减少年老代的空间,单次Minor GC的工夫反而会减少,如果是堆内存中短期对象多,那么扩容后,单词Minor GC的工夫不会显著的减少,还升高了Minor GC频率。升高Full GC频率Full GC的触发通常是因为堆内存空间有余或者老年代对象太多造成的,Full GC又会带来上下文切换,前边的文章咱们曾经专门介绍过上下文切换,都晓得上下文切换会升高零碎的性能。咱们能够通过下边几个方向来升高Full GC的频率。 ...

December 11, 2020 · 1 min · jiezi

关于垃圾回收机制:深入理解Chrome-V8垃圾回收机制

最近,我的项目进入保护期,根本没有什么需要,比拟闲,这让我莫名的有了危机感,每天像是在混日子,感觉这像是在温水煮青蛙,曾经毕业3年了,很怕本人到了5年教训的时候,能力却和3年教训的时候一样,没什么出息。于是开始整顿本人的技术点,刚好查漏补缺,在收藏夹在翻出了一篇文章一名【合格】前端工程师的自检清单,看到了外面的两个问题: JavaScript中的变量在内存中的具体存储模式是什么?浏览器的垃圾回收机制,如何防止内存透露?而后各种查资料,就整顿了这篇文章。 浏览本文之后,你能够理解到: JavaScript的内存是怎么治理的?Chrome是如何进行垃圾回收的?Chrome对垃圾回收进行了哪些优化?原文地址 欢送star JavaScript的内存治理不论什么程序语言,内存生命周期根本是统一的: 调配你所须要的内存应用调配到的内存(读、写)不须要时将其开释偿还与其余须要手动治理内存的语言不通,在JavaScript中,当咱们创立变量(对象,字符串等)的时候,零碎会主动给对象调配对应的内存。 var n = 123; // 给数值变量分配内存var s = "azerty"; // 给字符串分配内存var o = { a: 1, b: null}; // 给对象及其蕴含的值分配内存// 给数组及其蕴含的值分配内存(就像对象一样)var a = [1, null, "abra"]; function f(a){ return a + 2;} // 给函数(可调用的对象)分配内存// 函数表达式也能调配一个对象someElement.addEventListener('click', function(){ someElement.style.backgroundColor = 'blue';}, false);当零碎发现这些变量不再被应用的时候,会主动开释(垃圾回收)这些变量的内存,开发者不必过多的关怀内存问题。 尽管这样,咱们开发过程中也须要理解JavaScript的内存管理机制,这样能力防止一些不必要的问题,比方上面代码: {}=={} // false[]==[] // false''=='' // true在JavaScript中,数据类型分为两类,简略类型和援用类型,对于简略类型,内存是保留在栈(stack)空间中,简单数据类型,内存是保留在堆(heap)空间中。 根本类型:这些类型在内存中别离占有固定大小的空间,他们的值保留在栈空间,咱们通过按值来拜访的援用类型:援用类型,值大小不固定,栈内存中寄存地址指向堆内存中的对象。是按援用拜访的。而对于栈的内存空间,只保留简略数据类型的内存,由操作系统主动调配和主动开释。而堆空间中的内存,因为大小不固定,零碎无奈无奈进行主动开释,这个时候就须要JS引擎来手动的开释这些内存。 为什么须要垃圾回收在Chrome中,v8被限度了内存的应用(64位约1.4G/1464MB , 32位约0.7G/732MB),为什么要限度呢? 表层起因是,V8最后为浏览器而设计,不太可能遇到用大量内存的场景深层起因是,V8的垃圾回收机制的限度(如果清理大量的内存垃圾是很耗时间,这样回引起JavaScript线程暂停执行的工夫,那么性能和利用直线降落)后面说到栈内的内存,操作系统会主动进行内存调配和内存开释,而堆中的内存,由JS引擎(如Chrome的V8)手动进行开释,当咱们代码的依照正确的写法时,会使得JS引擎的垃圾回收机制无奈正确的对内存进行开释(内存泄露),从而使得浏览器占用的内存一直减少,进而导致JavaScript和利用、操作系统性能降落。 Chrome 垃圾回收算法在JavaScript中,其实绝大多数的对象存活周期都很短,大部分在通过一次的垃圾回收之后,内存就会被开释掉,而少部分的对象存活周期将会很长,始终是沉闷的对象,不须要被回收。为了进步回收效率,V8 将堆分为两类新生代和老生代,新生代中寄存的是生存工夫短的对象,老生代中寄存的生存工夫久的对象。 新生区通常只反对 1~8M 的容量,而老生区反对的容量就大很多了。对于这两块区域,V8 别离应用两个不同的垃圾回收器,以便更高效地施行垃圾回收。 ...

September 26, 2020 · 1 min · jiezi

全栈之路JAVA基础课程二20190611v10

欢迎进入JAVA基础课程二 本系列文章将主要针对JAVA一些基础知识点进行讲解,为平时归纳所总结,不管是刚接触JAVA开发菜鸟还是业界资深人士,都希望对广大同行带来一些帮助。若有问题请及时留言或加QQ:243042162。 谨记:经历过国考、省考,参加过各种证书考试,无疑对于上了年纪的人来说时刻有着莫大的危机感,时刻想着如何摆脱目前的困境。经常跟身边的同事去讨论20年后你在这个行业处于什么位置,当想想如果20年后还处于现在的位置是多么可怕的一件事情。悟性可以差点,时间可晚点,但学习跟积累必须跟上,与时俱进,搬砖人才可能脱胎换骨,收割事业的成就感与满足感。数据类型1. 基础数据类型: 数值型 整型数据(byte |short |int |long)浮点类型(float |double)字符型(char)布尔型(boolean)2. 引用数据类型: 类(class)接口(interface)数组注意事项&:左右都判断,可作位运算和逻辑与运算符&&:左假则结束,左真判断右,可作逻辑与运算符垃圾回收机制之前网络上见过两个很搞笑的图比喻C语言和JAVA之间的垃圾回收机制,如下图所示 C语言 JAVA语言 (1)C的垃圾回收是人工的,工作量大,但是可控性高。(2)JAVA是自动化的,但是可控性很差,甚至有时会出现内存溢出的情况。(3)System.gc(),用于调用垃圾收集器,在调用时,垃圾收集器将运行以回收未使用的内存空间。它将尝试释放被丢弃对象占用的内存。然而System.gc()调用附带一个免责声明,无法保证对垃圾收集器的调用。所以System.gc()并不能说是完美主动进行了垃圾回收。1、确定哪些对象要进行回收经典算法:引用计数法、可达性分析算法 2、什么时候进行回收会在cpu空闲的时候自动进行回收在堆内存存储满了之后主动调用System.gc()后尝试进行回收3、如何回收相关算法:标记-清除算法、复制算法、标记-整理算法、分代收集算法 标记-清除算法 (效率和内存碎片问题):这是最基础的一种算法,分为两个步骤,第一个步骤就是标记,也就是标记处所有需要回收的对象,标记完成后就进行统一的回收掉哪些带有标记的对象。这种算法优点是简单,缺点是效率问题,还有一个最大的缺点是空间问题,标记清除之后会产生大量不连续的内存碎片,当程序在以后的运行过程中需要分配较大对象时无法找到足够的连续内存而造成内存空间浪费。 复制算法(适用于对象存活率低的场景) :复制将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这样使得每次都是对其中的一块进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况。只是这种算法的代价是将内存缩小为原来的一半。 标记整理算法(适用于对象存活率高的场景) :标记整理算法与标记清除算法很相似,但最显著的区别是:标记清除算法仅对不存活的对象进行处理,剩余存活对象不做任何处理,造成内存碎片;而标记整理算法不仅对不存活对象进行处理清除,还对剩余的存活对象进行整理,重新整理,因此其不会产生内存碎片。 分代收集算法 (根据存活周期分为不同的几块):分代收集算法是一种比较智能的算法,也是现在jvm使用最多的一种算法,他本身其实不是一个新的算法,而是他会在具体的场景自动选择以上三种算法进行垃圾对象回收。

June 11, 2019 · 1 min · jiezi

java-jvm

什么是垃圾回收机制不定时去堆内存中清理不可达对象。不可达的对象并不会马上就会直接回收, 垃圾收集器在一个Java程序中的执行是自动的,不能强制执行,即使程序员能明确地判断出有一块内存已经无用了,是应该回收的,程序员也不能强制垃圾收集器回收该内存块。程序员唯一能做的就是通过调用System.gc 方法来"建议"执行垃圾收集器,但其是否可以执行,什么时候执行却都是不可知的。这也是垃圾收集器的最主要的缺点。当然相对于它给程序员带来的巨大方便性而言,这个缺点是瑕不掩瑜的。 finalize方法作用Java技术使用finalize()方法在垃圾收集器将对象从内存中清除出去前,做必要的清理工作。这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的。它是在Object类中定义的,因此所有的类都继承了它。子类覆盖finalize()方法以整理系统资源或者执行其他清理工作。finalize()方法是在垃圾收集器删除对象之前对这个对象调用的。 新生代与老年代Java 中的堆是 JVM 所管理的最大的一块内存空间,主要用于存放各种类的实例对象。在 Java 中,堆被划分成两个不同的区域:新生代 ( Young )、老年代 ( Old )。新生代 ( Young ) 又被划分为三个区域:Eden、From Survivor、To Survivor。这样划分的目的是为了使 JVM 能够更好的管理堆内存中的对象,包括内存的分配以及回收。 (本人使用的是 JDK1.6,以下涉及的 JVM 默认值均以该版本为准。)默认的,新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ( 该值可以通过参数 –XX:NewRatio 来指定 ),即:新生代 ( Young ) = 1/3 的堆空间大小。老年代 ( Old ) = 2/3 的堆空间大小。其中,新生代 ( Young ) 被细分为 Eden 和 两个 Survivor 区域,这两个 Survivor 区域分别被命名为 from 和 to,以示区分。默认的,Edem : from : to = 8 : 1 : 1 ( 可以通过参数 –XX:SurvivorRatio 来设定 ),即: Eden = 8/10 的新生代空间大小,from = to = 1/10 的新生代空间大小。根据垃圾回收机制的不同,Java堆有可能拥有不同的结构,最为常见的就是将整个Java堆分为新生代和老年代。其中新生带存放新生的对象或者年龄不大的对象,老年代则存放老年对象。新生代分为den区、s0区、s1区,s0和s1也被称为from和to区域,他们是两块大小相等并且可以互相角色的空间。绝大多数情况下,对象首先分配在eden区,在新生代回收后,如果对象还存活,则进入s0或s1区,之后每经过一次新生代回收,如果对象存活则它的年龄就加1,对象达到一定的年龄后,则进入老年代。 ...

June 2, 2019 · 2 min · jiezi

作用域链、垃圾回收机制、闭包及其应用(oop)

执行环境、变量对象 / 活动对象、作用域链执行环境(executioncontext,为简单起见,有时也称为“环境”)是JavaScript中最为重要的一个概念。执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。每个执行环境都有一个与之关联的变量对象(variableobject),环境中定义的所有变量和函数都保存在这个对象中。虽然我们编写的代码无法访问这个对象,但解析器在处理数据时会在后台使用它。全局执行环境是最外围的一个执行环境。根据ECMAScript实现所在的宿主环境不同,表示执行环境的对象也不一样。在Web浏览器中,全局执行环境被认为是window对象,因此所有全局变量和函数都是作为window对象的属性和方法创建的。某个执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁(全局执行环境直到应用程序退出——例如关闭网页或浏览器——时才会被销毁)。每个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而在函数执行之后,栈将其环境弹出,把控制权返回返回给之前的执行环境。ECMAScript程序中的执行流正是由这个方便的机制控制着。当代码在一个环境中执行时,会创建变量对象的一个作用域链(scopechain)。作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问。作用域链的前端,始终都是当前执行的代码所在环境的变量对象。如果这个环境是函数,则将其活动对象(activationobject)作为变量对象。活动对象在最开始时只包含一个变量,即arguments对象(这个对象在全局环境中是不存在的)。作用域链中的下一个变量对象来自包含(外部)环境,而再下一个变量对象则来自下一个包含环境。这样,一直延续到全局执行环境;全局执行环境的变量对象始终都是作用域链中的最后一个对象。标识符解析是沿着作用域链一级一级地搜索标识符的过程。搜索过程始终从作用域链的前端开始,然后逐级地向后回溯,直至找到标识符为止(如果找不到标识符,通常会导致错误发生)。—- 摘自 JavaScript高级程序设计理论说完,直接上代码。function Fn() { var count = 0 function innerFn() { count ++ console.log(‘inner’, count) } return innerFn}var fn = Fn()document.querySelector(’#btn’).addEventListener(‘click’, ()=> { fn() Fn()()})1、 浏览器打开,进入全局执行环境,也就是window对象,对应的变量对象就是全局变量对象。在全局变量对象里定义了两个变量:Fn和fn。2、当代码执行到fn的赋值时,执行流进入Fn函数,Fn的执行环境被创建并推入环境栈,与之对应的变量对象也被创建,当Fn的代码在执行环境中执行时,会创建变量对象的一个作用域链,这个作用域链首先可以访问本地的变量对象(当前执行的代码所在环境的变量对象),往上可以访问来自包含环境的变量对象,如此一层层往上直到全局环境。Fn的变量对象里有两个变量:count和innerFn,其实还有arguments和this,这里先忽略。然后函数返回了innerFn函数出去赋给了fn。3、手动执行点击事件。首先,执行流进入了fn函数,实际上是进入了innerFn函数,innerFn的执行环境被创建并推入环境栈,执行innerFn代码,通过作用域链对Fn的活动对象中的count进行了+1,并且打印。执行完毕,环境出栈。然后,执行流进入了Fn函数,Fn的执行跟第2步的一样,返回了innerFn。接着执行了innerFn函数,innerFn的执行跟前面的一样。每一次点击都执行了fn, Fn, innerFn,而fn和innerFn其实是一样逻辑的函数,但控制台打印出来的结果却有所不同。点击了3次的结果,接下来进入闭包环节。闭包垃圾回收机制先介绍下垃圾回收机制。离开作用域的值将被自动标记为可以回收,因此将在垃圾收集期间被删除。“标记清除”是目前主流的垃圾收集算法,这种算法的思想是给当前不使用的值加上标记,然后再回收其内存。—- 摘自 JavaScript高级程序设计通俗点说就是:1、函数执行完了,其执行环境会出栈,其变量对象自然就离开了作用域,面临着被销毁的命运。但是如果其中的某个变量被其他作用域引用着,那么这个变量将继续保持在内存当中。2、全局变量对象在浏览器关闭时才会被销毁。接下来看看上面的代码。对了先画张图。现在就解释下为什么会有不同的结果。Fn()() — 执行Fn函数,return了innerFn函数并立即执行了innerFn函数,因为innerFn函数引用了Fn变量对象中的count变量,所以即使Fn函数执行完了,count变量还是保留在内存中。等innerFn执行完了,引用也随之消失,此时count变量被回收。所以每次运行Fn()(),count变量的值都是1。fn() — 从fn的赋值开始说起,Fn函数执行后return了innerFn函数赋值给了fn。从这个时候开始Fn的变量对象中的count变量就被innerFn引用着,而innerFn被fn引用着,被引用的都存在于内存中。然后执行了fn函数,实际上执行了存在于内存中的innerFn函数,存在于内存中的count++。执行完成后,innerFn还是被fn引用着,由于fn是全局变量除了浏览器关闭外不会被销毁,以至于这个innerFn函数没有被销毁,再延申就是innerFn引用的count变量也不会被销毁。所以每次运行fn函数实际上执行的还是那个存在于内存中的innerFn函数,自然引用的也是那个存在于内存中的count变量。不像Fn()(),每次的执行实际上都开辟了一个新的内存空间,执行的也是新的Fn函数和innerFn函数。闭包的用途1、通过作用域访问外层函数的私有变量/方法,并且使这些私有变量/方法保留再内存中2、避免全局变量的污染3、代码模块化 / 面向对象编程oop举个例子function Animal() { var hobbies = [] return { addHobby: name => {hobbies.push(name)}, showHobbies: () => {console.log(hobbies)} }}var dog = Animal()dog.addHobby(’eat’)dog.addHobby(‘sleep’)dog.showHobbies()定义了一个Animal的方法,里面有一个私有变量hobbies,这个私有变量外部无法访问。全局定义了dog的变量,并且把Animal执行后的对象赋值给了dog(其实dog就是Animal的实例化对象),通过dog对象里的方法就可以访问Animal中的私有属性hobbies。这么做可以保证私有属性只能被其实例化对象访问,并且一直保留在内存中。当然还可以实例化多个对象,每个实例对象所引用的私有属性也互不相干。当然还可以写成构造函数(类)的方式function Animal() { var hobbies = [] this.addHobby = name => {hobbies.push(name)}, this.showHobbies = () => {console.log(hobbies)}}var dog = new Animal() ...

March 19, 2019 · 1 min · jiezi