关于垃圾回收:如何选择一款适合自己应用的垃圾收集器呢

垃圾收集器选型因素应用程序的次要关注点是什么?如果是数据分析、科学计算类的工作,指标是尽快算出后果,那吞吐量就是次要关注点;如果是SLA利用,那进展工夫间接影响服务质量,重大的甚至会导致事物超时,这样提早就是次要的关注点;而如果是客户端利用或者嵌入式应用,那垃圾收集的内存占用则是侧重点。运行利用的基础设施如何?譬如硬件规格,要设计的零碎时x86-32/64、SPARC还是ARM/Aarch64;处理器的数量是多少,调配的内存大小;抉择的操作系统是Linux、Solaris还是Windos等。应用的JDK的发行版是什么?版本号是多少?是ZingJDK/Zulu、OracleJDK、OpenJDK、OpenJ9抑或是其余公司的发行版?该JDK对应了《Java虚拟机标准》的哪个版本?一般来说收集器的抉择就从以上几点思考,举个例子,假如某个间接面向用户提供服务的B/S零碎筹备抉择垃圾收集器,一般来说延迟时间是这类利用的次要关注点,那么: 如果你有短缺的估算单没有太多调优教训,那么一套代商业技术支持的专有硬件或者软件解决方案是不错的抉择,Azul公司以前主推的Vega零碎和当初主推的Zing VM是这方面的代表,这样你就能够应用传说中的C4收集器了。如果你尽管没有足够估算去应用商业解决方案,但可能掌控软硬件型号,应用较新的版本,同时又特地重视提早,那ZGC很值得尝试。如果你对还处于试验状态的收集器的稳定性有所顾虑,或者利用必须运行在Windos操作系统下,那ZGC就无缘了,试试Shenandoah吧。如果你接手的是遗留零碎,软硬件基础设施和JDK版本都比较落后,那就依据内存规模掂量一下,对于大略4GB到6GB以下的堆内存,CMS个别能解决的比拟好,而对于更大的堆内存,可重点考查一下G1。

April 27, 2023 · 1 min · jiezi

关于垃圾回收:你真的理解Java垃圾回收吗万字长文带你彻底搞懂垃圾回收机制

本文已参加掘金创作者训练营第三期「话题写作」赛道,详情查看:掘力打算|创作者训练营第三期正在进行,「写」出集体影响力。 Java 垃圾回收机制垃圾回收次要关注 Java 堆 Java 内存运行时区域中的程序计数器、虚拟机栈、本地办法栈随线程而生灭;栈中的栈帧随着办法的进入和退出而井井有条地执行着出栈和入栈操作。每一个栈帧中调配多少内存基本上是在类构造确定下来时就已知的(只管在运行期会由 JIT 编译器进行一些优化),因而这几个区域的内存调配和回收都具备确定性,不须要过多思考回收的问题,因为办法完结或者线程完结时,内存天然就跟随着回收了。 而 Java 堆不一样,一个接口中的多个实现类须要的内存可能不一样,一个办法中的多个分支须要的内存也可能不一样,咱们只有在程序处于运行期间时能力晓得会创立哪些对象,这部分内存的调配和回收都是动静的,垃圾收集器所关注的是这部分内存。 判断哪些对象须要被回收有以下两种办法: 援用计数法给对象增加一援用计数器,被援用一次计数器值就加 1;当援用生效时,计数器值就减 1;计数器为 0 时,对象就是不可能再被应用的,简略高效,毛病是无奈解决对象之间互相循环援用的问题。可达性剖析算法通过一系列的称为 "GC Roots" 的对象作为起始点,从这些节点开始向下搜寻,搜寻所走过的门路称为援用链(Reference Chain),当一个对象到 GC Roots 没有任何援用链相连时,则证实此对象是不可用的。此算法解决了上述循环援用的问题。在Java语言中,可作为 GC Roots 的对象包含上面几种:a. 虚拟机栈(栈帧中的本地变量表)中援用的对象。b. 办法区中类动态属性援用的对象。c. 办法区中常量援用的对象。d. 本地办法栈中 JNI(Native办法)援用的对象 作为 GC Roots 的节点次要在全局性的援用与执行上下文中。要明确的是,tracing gc必须以以后存活的对象集为 Roots,因而必须选取确定存活的援用类型对象。 GC 治理的区域是 Java 堆,虚拟机栈、办法区和本地办法栈不被 GC 所治理,因而选用这些区域内援用的对象作为 GC Roots,是不会被 GC 所回收的。 其中虚拟机栈和本地办法栈都是线程公有的内存区域,只有线程没有终止,就能确保它们中援用的对象的存活。而办法区中类动态属性援用的对象是显然存活的。常量援用的对象在以后可能存活,因而,也可能是 GC roots 的一部分。 强、软、弱、虚援用JDK1.2 以前,一个对象只有被援用和没有被援用两种状态。 起初,Java 对援用的概念进行了裁减,将援用分为强援用(Strong Reference)、软援用(Soft Reference)、弱援用(Weak Reference)、虚援用(Phantom Reference)4 种,这 4 种援用强度顺次逐步削弱。 强援用就是指在程序代码之中普遍存在的,相似"Object obj=new Object()"这类的援用,垃圾收集器永远不会回收存活的强援用对象。软援用:还有用但并非必须的对象。在零碎 将要产生内存溢出异样之前 ,将会把这些对象列进回收范畴之中进行第二次回收。弱援用也是用来形容非必须对象的,被弱援用关联的对象 只能生存到下一次垃圾收集产生之前 。当垃圾收集器工作时,无论内存是否足够,都会回收掉只被弱援用关联的对象。虚援用是最弱的一种援用关系。 无奈通过虚援用来获得一个对象实例 。为一个对象设置虚援用关联的惟一目标就是能在这个对象被收集器回收时收到一个零碎告诉。 可达性剖析算法不可达的对象将临时处于“缓刑”阶段,要真正宣告一个对象死亡,至多要经验两次标记过程: 如果对象在进行可达性剖析后发现没有与 GC Roots 相连接的援用链,那它将会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行 finalize() 办法。当对象没有笼罩 finalize() 办法,或者 finalize() 办法曾经被虚拟机调用过,虚拟机将这两种状况都视为“没有必要执行”,间接进行第二次标记。如果这个对象被断定为有必要执行 finalize() 办法,那么这个对象将会搁置在一个叫做 F-Queue 的队列之中,并在稍后由一个由虚拟机主动建设的、低优先级的 Finalizer 线程去执行它。这里所谓的“执行”是指虚构机会触发这个办法,但并不承诺会期待它运行完结,因为如果一个对象在 finalize() 办法中执行迟缓,将很可能会始终阻塞 F-Queue 队列,甚至导致整个内存回收零碎解体。 ...

August 30, 2021 · 13 min · jiezi

关于垃圾回收:九神带你入门JVM下

咱们接着下面一篇持续学习JVM的基本知识。 对象存活判断上篇中咱们介绍过JVM垃圾回收综述中说过一次垃圾回收之后会有一些对象存活。这节咱们介绍两个判断对象存活的算法。 判断对象存活有援用计数算法和可达性剖析算法。 1、援用计数算法 给每一个对象增加一个援用计数器,每当有一个中央援用它时,计数器值加1;每当有一个中央不再援用它时,计数器值减1,这样只有计数器的值不为0,就阐明还有中央援用它,它就不是无用的对象。 这种办法看起来非常简单,但目前许多支流的虚拟机都没有选用这种算法来治理内存,起因就是当某些对象之间相互援用时,无奈判断出这些对象是否已死。如下图,对象1和对象2都没有被堆外的变量援用,而是被对方相互援用,这时他们尽管没有用途了,然而援用计数器的值依然是1,无奈判断他们是死对象,垃圾回收器也就无奈回收。 2、可达性剖析算法 理解可达性剖析算法之前先理解一个概念——GC Roots,垃圾收集的终点,能够作为GC Roots的有虚拟机栈中本地变量表中援用的对象、办法区中动态属性援用的对象、办法区中常量援用的对象、本地办法栈中JNI(Native办法)援用的对象。 当一个对象到GC Roots没有任何援用链相连(GC Roots到这个对象不可达)时,就阐明此对象是不可用的,是死对象。如下图:object1、object2、object3、object4和GC Roots之间有可达门路,这些对象不会被回收,但object5、object6、object7到GC Roots之间没有可达门路,这些对象就是死对象。 下面被断定为非存活的死对象(object5、object6、object7)并不是必死无疑,还有解救的余地。进行可达性剖析后对象和GC Roots之间没有援用链相连时,对象将会被进行一次标记,接着会判断如果对象没有笼罩Object的finalize()办法或者finalize()办法曾经被虚拟机调用过,那么它们就会革除;如果对象笼罩了finalize()办法且还没有被调用,则会执行finalize()办法中的内容,所以在finalize()办法中如果从新与GC Roots援用链上的对象关联就能够援救本人。当然,理论中个别不会这么做。 GC算法接下来讲GC的算法,次要有标记-革除算法、复制算法、标记-整顿算法、分代收集算法。 1、标记-革除算法 最根底的收集算法是“标记-革除”(Mark-Sweep)算法,分两个阶段:首先标记出所有须要回收的对象,在标记实现后对立回收所有被标记的对象。 长处:不须要进行对象的挪动,并且仅对不存活的对象进行解决,在存活对象比拟多的状况极为无效。 有余:一个是效率问题,标记和革除两个过程的效率都不高;另一个是空间问题,标记革除之后会产生大量不间断的内存碎片,空间碎片太多可能导致当前在程序运行过程须要调配较大对象时,无奈找到足够的间断内存而不得不提前触发另一个的垃圾收集动作。 上面两张图从两个角度说明了标记-分明算法: 2、复制算法 为了解决效率问题,一种称为复制(Copying)的收集算法呈现了,它将可用内存按容量划分为大小相等的两块,每次只应用其中的一块。当这一块内存用完了,就将还存活着的对象复制到另外一块上,而后再把曾经应用过的内存空间一次清理掉。这样使得每次都是对整个半区进行内存回收,内存调配时也就不必思考内存碎片等简单状况,只有挪动堆顶指针,按程序分配内存即可,实现简略,运行高效。代价是内存放大为原来的一半。 复制算法过程如上面两张图示意: 商业虚拟机用这个回收算法来回收新生代。IBM钻研表明98%的对象是“朝生夕死“,不须要依照1-1的比例来划分内存空间,而是将内存分为一块较大的”Eden“空间和两块较小的Survivor空间,每次应用Eden和其中一块Survivor。当回收时,将Eden和Survivor中还存活的对象一次性复制到另外一个Survivor空间上,最初清理掉Eden和方才用过的Survivor空间。Hotspot虚拟机默认Eden和Survivor的比例是8-1.即每次可用整个新生代的90%, 只有一个survivor,即1/10被”节约“。当然,98%的对象回收只是个别场景下的数据,咱们没有方法保障每次回收都只有不多于10%的对象存活,当Survivor空间不够时,须要依赖其余内存(老年代)进行调配担保(Handle Promotion). 如果另外一块survivor空间没有足够空间寄存上一次新生代收集下来的存活对象时,这些对象将间接通过调配担保机制进入老年代。 上面大略介绍一下这个eden survivor复制的过程。 Eden Space字面意思是伊甸园,对象被创立的时候首先放到这个区域,进行垃圾回收后,不能被回收的对象被放入到空的survivor区域。 Survivor Space幸存者区,用于保留在eden space内存区域中通过垃圾回收后没有被回收的对象。Survivor有两个,别离为To Survivor、 From Survivor,这个两个区域的空间大小是一样的。执行垃圾回收的时候Eden区域不能被回收的对象被放入到空的survivor(也就是To Survivor,同时Eden区域的内存会在垃圾回收的过程中全副开释),另一个survivor(即From Survivor)里不能被回收的对象也会被放入这个survivor(即To Survivor),而后To Survivor 和 From Survivor的标记会调换,始终保障一个survivor是空的。 为啥须要两个survivor?因为须要一个残缺的空间来复制过去。当满的时候降职。每次都往标记为to的外面放,而后调换,这时from曾经被清空,能够当作to了。 3、标记-整顿算法 复制收集算法在对象成活率较高时就要进行较多的复制操作,效率将会变低。更要害的是,如果不想节约50%的空间,就须要有额定的空间进行调配担保,以应答被应用的内存中所有对象都100%存活的极其状况,所以,老年代个别不能间接选用这种算法。 依据老年代的特点,有人提出一种”标记-整顿“Mark-Compact算法,标记过程依然和标记-革除一样,但后续步骤不是间接对可回收对象进行清理,而是让所有存活的对象都向一端挪动,而后间接清理端边界以外的内存。 上面两张图讲了这个算法的过程: 4、分代收集算法 以后商业虚拟机的垃圾收集都采纳”分代收集“(Generational Collection)算法,这种算法依据对象存活周期的不同将内存划分为几块。个别把Java堆分为新生代和老年代,这样就能够依据各个年代的特点采纳最适当的收集算法。在新生代,每次垃圾收集时都发现少量对象死去,只有大量存活,那就选用复制算法,只须要付出大量存活对象的复制老本就能够实现收集。而老年代中因为对象存活率较高,没有额定的空间对它进行调配担保,就必须应用”标记-清理“和”标记-整顿“算法来进行回收。 这种算法就是咱们在后面JVM垃圾回收综述中讲述的内容。其本质是更为灵便的应用”标记-清理“和”标记-整顿“算法。 常见的GC回收器当初常见的垃圾收集器有如下几种 ...

November 23, 2020 · 2 min · jiezi

关于垃圾回收:垃圾回收的算法与实现pdf

关注“Java后端技术全栈” 回复“面试”获取全套面试材料 什么是GC? GC是Garbage Collection的简称,中文称为“垃圾回收”。 垃圾的回收Garbage Collection的Garbage,也就是“垃圾”,具体指的是什么呢? 在事实世界中,说到垃圾,指的就是那些不读的书、不穿的衣服等。这种状况下的“垃圾”指的是“本人不必的货色”。在GC中,“垃圾”的定义也是如此。GC把程序不必的内存空间视为垃圾。 GC到底会给程序员带来怎么的益处呢? 没有GC的世界在没有GC的世界里,程序员必须本人手动进行内存治理,必须分明地确保必要的内存空间,开释不要的内存空间。 为了省去上述手动内存治理的麻烦,人们钻研开发出了GC。如果把内存治理交给计算机,程序员就不必去想着开释内存了。 有了GC,程序员就不必再去放心因为忘了开释内存等而导致BUG,从而大大加重了累赘。 最近很多小伙伴问我要一些 GC 相干的材料,于是我翻箱倒柜,找到了这本十分经典的电子书——《垃圾回收的算法与实现》。 材料介绍 《垃圾回收的算法与实现》由多位IT界的大佬联结举荐,被称为是“一本书把握主动内存回收的机制!”。全书分为“算法篇”和“实现篇”两大部分。算法篇介绍了各种算法,实现篇介绍了垃圾回收在Python、DalvikVM、Rubinius、V8等几种语言处理程序中的具体实现。配合大量形象的插图和代码,将各个知识点掰开揉碎解说,非常适合入门学习。 如何获取? 1.辨认二维码并关注公众号「Java后端技术全栈」; 2.在公众号后盾回复关键字「962」

November 19, 2020 · 1 min · jiezi

关于垃圾回收:UE4-垃圾回收

UE4引擎为咱们搭建了一套UObject对象零碎,并且退出了垃圾回收机制,使咱们用C++进行游戏开发时更加不便,而且游戏自身也能够极大水平地防止内存透露问题。 UE4引擎采纳了标记-打扫垃圾回收形式,是一种经典的垃圾回收形式。一次垃圾回收分为两个阶段:第一阶段从一个根汇合登程,遍历所有可达对象,遍历实现后就能标记出可达对象和不可达对象了,这个阶段会在一帧内实现;第二阶段会渐进式地清理这些不可达对象,因为不可达的对象将永远不能被拜访到,所以能够分帧清理它们,防止一下子清理很多UObject。比方,Map卸载时就会产生显著的卡顿。 GC产生在游戏线程上,对UObject进行清理,反对多线程GC。 对GC能够设置若干参数,比方MaxObjectsInGame,规定了游戏中最大存在的UObject对象(对编辑器不失效),挪动平台上默认设置了131072。当UObject数量超过这个阈值时,游戏会解体,其余具体参数可见UGarbageCollectionSettings、GarbageCollection.cpp和UnrealEngine.cpp中相干的属性。 下图为标记-打扫的工作原理: 1.1 GC何时进行UE4引擎中GC能够分为被动引发和主动引发两种形式。 1.1.1 被动引发能够在执行一些操作时手动调用GC。比方,卸载一个资源后,立刻调用一次GC进行清理。 游戏中能够调用ForceGarbageCollection来让World下次Tick时进行垃圾回收,也能够间接调用CollectGarbage进行垃圾回收(引擎中大部分状况都用这种形式被动引发)。 1.1.2 主动引发游戏中,大部分的垃圾回收操作都是由UE4引擎主动引发的,一般状况下不须要手动调用GC,这也是现实的GC应用形式。 当World进行Tick时,会调用UEngine::ConditionalCollectGarbage()函数,函数中进行了一些判断,当满足GC条件时,才会执行GC。上面剖析一下ConditionalCollectGarbage的执行逻辑。 UE4 GC流程 和 清理流程 可返回UWA学堂收费查看。

November 13, 2020 · 1 min · jiezi

咱们从头到尾说一次-Java-垃圾回收

之前上学的时候有这个一个梗,说在食堂里吃饭,吃完把餐盘端走清理的,是 C++ 程序员,吃完直接就走的,是 Java 程序员。 确实,在 Java 的世界里,似乎我们不用对垃圾回收那么的专注,很多初学者不懂 GC,也依然能写出一个能用甚至还不错的程序或系统。但其实这并不代表 Java 的 GC 就不重要。相反,它是那么的重要和复杂,以至于出了问题,那些初学者除了打开 GC 日志,看着一堆0101的天文,啥也做不了。 今天我们就从头到尾完整地聊一聊 Java 的垃圾回收。 什么是垃圾回收垃圾回收(Garbage Collection,GC),顾名思义就是释放垃圾占用的空间,防止内存泄露。有效的使用可以使用的内存,对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收。Java 语言出来之前,大家都在拼命的写 C 或者 C++ 的程序,而此时存在一个很大的矛盾,C++ 等语言创建对象要不断的去开辟空间,不用的时候又需要不断的去释放控件,既要写构造函数,又要写析构函数,很多时候都在重复的 allocated,然后不停的析构。于是,有人就提出,能不能写一段程序实现这块功能,每次创建,释放控件的时候复用这段代码,而无需重复的书写呢?1960年,基于 MIT 的 Lisp 首先提出了垃圾回收的概念,用于处理C语言等不停的析构操作,而这时 Java 还没有出世呢!所以实际上 GC 并不是Java的专利,GC 的历史远远大于 Java 的历史!怎么定义垃圾既然我们要做垃圾回收,首先我们得搞清楚垃圾的定义是什么,哪些内存是需要回收的。 引用计数算法引用计数算法(Reachability Counting)是通过在对象头中分配一个空间来保存该对象被引用的次数(Reference Count)。如果该对象被其它对象引用,则它的引用计数加1,如果删除对该对象的引用,那么它的引用计数就减1,当该对象的引用计数为0时,那么该对象就会被回收。 String m = new String("jack"); 先创建一个字符串,这时候"jack"有一个引用,就是 m。 然后将 m 设置为 null,这时候"jack"的引用次数就等于0了,在引用计数算法中,意味着这块内容就需要被回收了。 m = null; 引用计数算法是将垃圾回收分摊到整个应用程序的运行当中了,而不是在进行垃圾收集时,要挂起整个应用的运行,直到对堆中所有对象的处理都结束。因此,采用引用计数的垃圾收集不属于严格意义上的"Stop-The-World"的垃圾收集机制。 看似很美好,但我们知道JVM的垃圾回收就是"Stop-The-World"的,那是什么原因导致我们最终放弃了引用计数算法呢?看下面的例子。 public class ReferenceCountingGC {public Object instance;public ReferenceCountingGC(String name){}}public static void testGC(){ReferenceCountingGC a = new ReferenceCountingGC("objA");ReferenceCountingGC b = new ReferenceCountingGC("objB");a.instance = b;b.instance = a;a = null;b = null;}1. 定义2个对象2. 相互引用3. 置空各自的声明引用 ...

July 15, 2019 · 3 min · jiezi

一个阿里产品经理眼中的垃圾分类

我叫切斯,是阿里巴巴的一名产品经理。今天和大家说说一个上线刚刚6天,已被500多万网友疯玩的AI——垃圾图像识别,可见垃圾分类苦天下网友久矣。 以下是一个产品经理的碎碎念~ 有人说它是“国内首款真正的垃圾图像识别产品”,对着物品拍照就可以知道这是哪一类垃圾。我不知道是不是真的首款,网友说是那就姑且是吧。 话说网上热议垃圾分类时,阿里的小二们也没闲着,内网里展开了热闹的讨论: “AR扫一扫”的解决方案被提了出来,但是质疑的声音也很大—— “到底能不能行”“用户真的需要这个吗”…… 真正让这个“孩子”获得准生证的理由,是因为公益与责任的力量。正如一位阿里合伙人所说,“AI的谐音是‘爱’,大家对‘AI’是有宽容度的;而且只有人人都能为这个‘AI’而努力,共同参与起来,才能让他更好”。“现在还是在上海这样的一线城市,用户对垃圾分类就已经如此困惑。未来这个意识扩展到中国的每一片土地时,人们一定会更加需要这样的帮助。阿里巴巴一定要为这个社会去承担责任”。 经过48小时的紧急开发,这个AI智能识别垃圾功能(测试版)在手机淘宝上线,功能通过阿里云搭建。 为了更准确实现AI识别,阿里的工程师志愿者们还与公益环保组织合作,紧急搭建了首个互联网垃圾分类数据库;天猫精灵团队甚至连夜支持了图像识别之外的文本识别的能力。 为了让它快速学习,程序员和设计师小哥让我把它扔到了阿里内网里接受测试和训练。 在这48个小时里,为了让孩子早点诞生,开发同学一下飞机就匆匆赶回公司;设计师小哥凌晨三点还在改视觉设计;程序员凌晨两点对我这个产品经理说:“你放心回去吧,今天不管多晚我一定把bug 改完发布上线……” 30多位志愿者分别来自社会公益、达摩院、天猫精灵、闲鱼、淘宝等团队。 “垃圾分类”的AI上线后,身边的每个人都拿着手机做测试,我的心情是既开心又紧张。兴奋的是越来越多的志愿者主动加入到了推广这个AI,“抚养”它“教育”它的行列,他们来自各行各业,有年过七旬的老人,也有在校的大学生……我知道这只是个开始,距离它真正长大还有很长很长的路要走。 在淘宝AI垃圾分类识别系统上线前后,支付宝、天猫精灵、盒马等APP也推出了不同的垃圾识别功能。就在刚刚,我们淘宝拍立淘还联动闲鱼对手淘AI识别功能进行了升级,闲鱼将免费上门回收网友识别出来的可回收垃圾。 改变世界的不是技术,而是技术背后的温度。改变世界的不是空想和讨论,而是从我做起、从此刻做起的决心与行动。希望未来的一天,每个人都会感谢自己给这个世界带来的那些微小而美好的变化。 每个人的一点点改变,终将汇聚成大大的能量,从今天开始就比明天开始早行动了24个小时。 垃圾分类,从我做起!爱护环境,人人有责! 本文作者:会分类的橙阅读原文 本文来自云栖社区合作伙伴“阿里技术”,如需转载请联系原作者。

July 12, 2019 · 1 min · jiezi

JavaScript深入浅出第3课什么是垃圾回收算法

摘要: JS是如何回收内存的? 《JavaScript深入浅出》系列: JavaScript深入浅出第1课:箭头函数中的this究竟是什么鬼?JavaScript深入浅出第2课:函数是一等公民是什么意思呢?JavaScript深入浅出第3课:什么是垃圾回收算法?最近垃圾回收这个话题非常火,大家不能随随便便的扔垃圾了,还得先分类,这样方便对垃圾进行回收再利用。 其实,对于写代码来说,也有垃圾回收(garbage collection)这个问题,这里所说的垃圾,指的是程序中不再需要的内存空间,垃圾回收指的是回收这些不再需要的内存空间,让程序可以重新利用这些释放的内存空间。 手动管理内存对于C这种底层语言来说,我们可以使用malloc()函数分配内存空间,当所分配的内存不再需要的时候,可以使用free()函数来释放内存空间。 #include <stdio.h>#include <stdlib.h>#define TRUE 1int main (){ int *p, i, n, sum; while (TRUE) { printf ("请输入数组长度: "); scanf ("%d", &n); p = (int *) malloc (n * sizeof (int)); // 分配内存空间 sum = 0; for (i = 0; i < n; ++i) { *(p + i) = i + 1; sum += *(p + i); } printf ("sum = %d\n", sum); free (p); // 释放内存空间 } return 0;}示例代码很简单,输入一个整数n,程序计算1、2、3...n的和。大家可以在Online C Compiler上运行这段代码。 ...

July 3, 2019 · 1 min · jiezi

大数据告诉你垃圾围城现行的垃圾分类制度作用究竟能有多大

作者 | 钟 黛 编辑 | 唐也钦 设计 | 邹 磊 眼前的湿不是湿,你说的干是什么干?站在垃圾分类桶前,DT君的卡姿兰大眼睛里充满疑惑。 最近几周,魔都群众不谈股票和房价,一门心思牵挂着猪:猪能吃的叫湿垃圾,猪都不吃的叫干垃圾,猪吃了会死的叫有害垃圾,可以卖出去换猪的叫可回收垃圾……一波激情学习之后,大考即将来临。 7月1日起,上海将正式开始实施《上海市生活垃圾管理条例》,推行垃圾定时定点分类投放制度,还会对垃圾分类不到位、拒不改正的单位和个人开出罚单。 其他城市的吃瓜群众恐怕也得对猪投入更多关心——据说,到2020年底,全国46个重点城市都得建成垃圾分类处理系统。 (图片说明:垃圾分类先行先试的46个重点城市,图片来源:住房和城乡建设部) 认真学习垃圾分类之余,DT君(公众号ID:DTcaijing)对于这背后的原因有诸多好奇:我们的城市遇到了怎样的垃圾问题?现阶段令人掩鼻的垃圾堆是如何被消化的?强制垃圾分类对于解决垃圾问题到底有啥帮助? 研究了许多数据与材料后,我们基本回答了上述问题。 1 大城市面临垃圾围城 北京比上海更严重 从数据来看,越光鲜的大城市,垃圾围城的问题越严重。 2017年,上海生活垃圾清运量为743.07万吨,北京更加庞大,达到924.77万吨——更具象来说,如果用载重5吨的大卡车来装这924.77万吨垃圾,两辆一排,可以从云南西双版纳直接排到哈尔滨。 以全国为观察范本,北京的生活垃圾生产力超过了24个省级行政单位,上海则超过了19个。 数据还顺带解释了大城市垃圾问题更严重的原因。 以2017年为例,我国各地区人均垃圾生产量与人均GDP的相关系数达到了0.83,这意味着城市的生活垃圾制造水平与经济发展水平高度相关,越发达的城市垃圾压力自然也越大。 北京群众的生活垃圾制造能力尤为强劲。 DT君计算了2017年全国各地区的人均生活垃圾清运量,没有意外,北京与上海稳居前两位,平均每个北京市民一年能制造出0.43吨垃圾,分摊到每天超过2斤——即使是考虑到城市的经济发展水平,这个生产力也是过于旺盛了。 2 我们生产的垃圾都流向了哪里 城市面临怎样的问题 一座成熟的城市,可以用尽量小的代价消化掉如此庞大的生活垃圾。 目前我国城市垃圾处理最主要的方式是填埋与焚烧,经过上述流程之后,生活垃圾就算是经过了无害化处理。 垃圾填埋的操作很简单,把各处的垃圾拉到填埋场,埋完后等待垃圾发生各种反应,慢慢分解、减量。由于成本比较低,现在为止仍旧是最主要的垃圾处理方式。 但填埋的弊端非常明显:占用大量土地、污染地下水、影响周边环境……生活垃圾在填埋的过程中,污染物随水分溶出,形成“渗沥液”,一般的垃圾填埋场都会制作防渗膜,但化工材料老化后,还是会有液体渗透出来。 相对来看,焚烧更加科学,占地相对比较少,垃圾减量也更加明显。纯理论层面讨论,近年来焚烧过程中产生的二噁英、PM2.5等污染物水平已经极大降低,是目前被提倡得更多的垃圾处理方式。 表面看来,大城市的垃圾处理得挺好。 经过近十年的努力,北京和上海这两座“最垃圾”的城市,生活垃圾无害化处理比例都已经上升到接近100%,而且,通过焚烧来处理的生活垃圾越来越多。 2017年,北京一共焚烧处理了326.5万吨垃圾,比填埋处理的垃圾量少去三成,而上海这一年焚烧处理的垃圾数量已经差不多等同于填埋的量。 不过,对于这两座超级城市,以填埋方式处理的生活垃圾比例恐怕都还得继续降低。 填埋场占地面积大,尤其是在土地资源紧缺的大城市,垃圾填埋疲态尽显,两城的填埋场在不同的时期都曾严重超负荷运转。2017年,北京埋下的生活垃圾量比其原本可以负荷的多出了15%。 如此看来,要维持城市正常运转,继续无害化处理这么多的生活垃圾,还得继续增加城市生活垃圾焚烧处理能力。 但这也存在问题。 垃圾焚烧虽然在垃圾减量和热能利用(发电)上有较大优势,但其带来的二噁英污染物是地球上最致命的有毒物质之一。二噁英一旦进入人体,会长久驻留,破坏人类免疫系统、改变甲状腺激素和类固醇激素以及生殖功能,最为敏感的是影响人体发育,导致胎儿畸形。 当焚烧温度高于850℃时,二噁英就不会产生——前面我们已提到,现有技术已能达到这一点,但实际运行中部分焚烧厂不能够做到达标排放,群众对于垃圾焚烧厂普遍存在抵触情绪,要增加城市垃圾焚烧能力也挺难。 这从两座城市现有垃圾处理场的分布就可见一斑。 据DT君手动不完全统计,上海运营中的4个垃圾填埋场与10个焚烧场,基本都建在外环外区域。那些有幸建到外环内的处理场,也基本都被安排到所属行政区边界位置,可以尽量少地影响本区群众。 北京的垃圾场分布,看似比上海更均匀,实则也处处充满“小心机”。在各区自行建垃圾场、解决生活垃圾的大前提下,每个区都尽可能地把垃圾场建在区边界。 而地处皇城中心的东西城,实在是没有地修建垃圾场,只能选择直接“用钱解决问题”,通过市级调控将垃圾运往昌平区进行处理,支付异地补偿处理费,西城区城管委的预算报告中,就包括2019年生活垃圾处理费及异地经济补偿费2.7亿余元。 显然,从群众到各行政区,大家都不太愿意接下垃圾处理的活儿。不远的将来,如果没办法提升垃圾处理能力,大城市将越来越难实现生活垃圾的自产自消。 而在这个问题上,北京可能比上海要更加急迫。从2017年的生活垃圾处理数据来看,上海的垃圾焚烧能力仍有较大富余,为城市生活垃圾的上涨保留了一些空间;而北京,在垃圾填埋已经严重超出负荷的情况下,垃圾焚烧能力也已经接近饱和。 那么,大力推行垃圾分类,就可以有效帮助解决这个问题吗? 3 城市要消化这么多垃圾 分类真的有用吗 我们可能没办法用日本与欧美的垃圾分类逻辑来理解中国城市的一系列操作,这里得特别提到我国生活垃圾的构成比例。 DT君在查询资料时看到这样一组数据,2002年上海生活垃圾中,厨余果皮占到68.2%,而东京都可燃垃圾中厨余垃圾占比仅为37.4%,在生活垃圾整体占比更小(来源于《上海与东京生活垃圾处理回收体系之比较》)。 清华大学环境学院教授刘建国教授在接受《经济观察报》采访时也提到,我国生活垃圾最主要的构成部分是厨余垃圾,超过60%,有的地区甚至达到70%至80%。对比欧美国家,他们最主要的垃圾是纸张,厨余垃圾只占到25%。因为这样的差异,我国垃圾的最大特点就是湿,含水率很高;另一特点是臭,容易腐烂降解。 (图片说明:世界部分国家和地区生活垃圾组成成分;图片来源:《垃圾围城:比雾霾更触目惊心,我们都无处可逃》截图;原始数据来源不可考,仅供参考) 富含水分的厨余垃圾为焚烧处理带来困难,它们与其他生活垃圾混在一起,会降低垃圾整体的焚烧热值,提高了二次污染的控制难度。简单来说,湿垃圾不容易被燃烧至可抑制二噁英生成的850℃,这对焚烧的技术与成本都提出了更高要求。 所以,如果将垃圾进行分类,会有利于提升垃圾焚烧的效率。 据《北京市城市生活垃圾焚烧社会成本评估报告》测算,每年北京市二噁英可能致癌人数之和达到241人,假设生活垃圾经过妥善分类,每年致癌人数将从241人降低至182人。 ...

July 2, 2019 · 1 min · jiezi

Java垃圾回收调优

Java垃圾回收调优应该是提升应用吞吐量的最后一个选择。在你发现应用由于长时间垃圾回收导致了应用性能下降、出现超时的时候,应该考虑Java垃圾收集调优。如果你在日志里看到 java.lang.OutOfMemoryError: PermGen space错误,那么可以尝试使用 -XX:PermGen 和 -XX:MaxPermGen JVM选项去监控并增加Perm Gen内存空间。你也可以尝试使用-XX:+CMSClassUnloadingEnabled并查看使用CMS垃圾收集器的执行性能。如果你看到了大量的Full GC操作,那么你应该尝试增大老年代的内存空间。全面垃圾收集调优要花费大量的努力和时间,这里没有一尘不变的硬性调优规则。你需要去尝试不同的选项并且对这些选项进行对比,从而找出最适合自己应用的方案。

June 24, 2019 · 1 min · jiezi

JVM垃圾回收算法总结

垃圾回收算法有很多种,目前商业虚拟机常用的是分代回收算法,但最初并不是用这个算法的我们来看一下垃圾收集算法的背景知识 标记-清除算法最基础的垃圾回收算法,顾名思义,整个回收过程分两步:1.逐个标记2.统一回收该算法可以算是后来所有垃圾回收算法的基石(后续所有算法都有标记和清除这两步,只不过策略上有了一些优化)这里值得一说的是这个标记 虚拟机是如何判断一个对象是“活”还是“死”?因此又引出两种标记算法:1.引用计数算法引用计数算法非常简单且高效,当一个对象被引用一次则+1不再被引用则-1,当计数为0就是不可能在被使用的对象了,但是这种算法存在一个致命的缺陷:两个对象相互引用对方呢?所以,这种算法肯定不能用,pass掉2.可达性分析算法目前的标记算法主流实现都是用的可达性分析算法。就是以一个叫GC Roots的对象为起点,通过引用链向下搜索,如果一个对象通过引用链无法与GC Roots对象链接,就视为可回收对象,上面说的那种相互引用的情况自然也解决了。扩展:即使是可达性分析中不可达的对象也并不是非死不可,只是暂处‘缓刑’,真正宣告一个对象死亡至少还要经历两次标记过程:当被判定不可达之后那么他被第一次标记并进行筛选,若对象没有覆盖finalize()方法或者finalize()方法已经被虚拟机调用过就‘放生’,如果被判定需要执行finalize()方法就会被放到一个叫F-Queue的队列中进行第二次标记对象被再次被引用就会放生,否则就会被回收。 说到这里敏锐的小伙伴可能以及察觉到了,上面都在说引用所以引用的定义就显得尤为关键了JDK1.2后Java对引用的概念进行了扩充,将引用分为:强引用、软引用、弱引用、虚引用四种 强引用:用处很大,无论如何都不会被GC回收软引用:有一定用处但不大,内存实在不够才会在内存溢出之前回收掉弱引用:比软引用强度更弱一些,GC会让它多活一轮,下一轮就回收虚引用:必回收,唯一作用就是被GC回收时会收到一个系统通知复制算法前面说的标记-清除算法其实两个过程效率都很低,并且回收之后内存被‘抠出很多洞’内存碎片化严重,此时如果过来了一个较大的对象,找不到一整块连续的内存空间就不得不提前触发另外一次GC回收。而复制算法则选择将内存一分为二每次只使用其中一半,满了之后将存活的对象整齐复制到另一块干净的内存上,将剩下的碎片一次性擦除,简单高效。但是也存在一个很大的缺陷,那就是可用内存变为原来的一半了。 分代收集算法事实上后来IBM公司经过研究发现,98%的对象都是‘朝生夕死’,所以并不需要1:1的划分内存,即我们现在常用的分代收集算法:根据对象的存活周期将内存划分为两块,分别为新生代和老年代,然后对各代采用不同的回收算法,在新生代中大部分是‘朝生夕死’的对象,继续将新生代8:2划分为Eden区和survival区,其中survival区1:1分成s0和s1两块,采用之前说的复制算法,减少内存碎片的产生。新生代满了会进行一次minor GC ,minor GC 存活的对象转移到survival区,survival区满了就会将survival区进行回收,存活的survival区对象复制到另外一块survival区中,并且survival区对象每存活一轮年龄+1当到达一定年龄就会前往老年代。 扩展:JVM何时会进行全局GC 01.手动调用System.GC 但也不是立即调用02.老年代空间不足03.永生代空间不足04.计算得知新生代前往老年代平均值大于老年代剩余空间

May 7, 2019 · 1 min · jiezi

Node-内存管理和垃圾回收

前言从前端思维转变到后端, 有一个很重要的点就是内存管理。以前写前端因为只是在浏览器上运行, 所以对于内存管理一般不怎么需要上心, 但是在服务器端, 则需要斤斤计较内存。 V8的内存限制和垃圾回收机制内存限制内存限制一般的后端语言开发中, 在基本的内存使用是没有限制的。 但由于Node是基于V8构建的, 而V8对于内存的使用有一定的限制。 在默认情况下, 64位的机器大概可以使用1.4G, 而32则为0.7G的大小。关于为什么要限制内存大小, 有两个方面。一个是V8一开始是为浏览器服务的, 而在浏览器端这样的内存大小是绰绰有余的。另一个则是待会提到的垃圾回收机制, 垃圾回收会暂停Js的运行, 如果内存过大, 就会导致垃圾回收的时间变长, 从而导致Js暂停的时间过长。 当然, 我们可以在启动Node服务的时候, 手动设置内存的大小 如下: node --max-old-space-size=768 // 设置老生代, 单位为MB node --max-semi-space-size=64 // 设置新生代, 单位为MB查看内存 在Node环境中, 可以通过process.memoryUsage()来查看内存分配 rss(resident set size):所有内存占用,包括指令区和堆栈heapTotal:V8引擎可以分配的最大堆内存,包含下面的 heapUsedheapUsed:V8引擎已经分配使用的堆内存external: V8管理C++对象绑定到JavaScript对象上的内存事实上, 对于大文件的操作通常会使用Buffer, 究其原因就是因为Node中内存小的原因, 而使用Buffer是不受这个限制, 它是堆外内存, 也就是上面提到的external。 v8的内存分代目前没有一种垃圾自动回收算法适用于所有场景, 所以v8的内部采用的其实是两种垃圾回收算法。他们回收的对象分别是生存周期较短和生存周期较长的两种对象。关于具体的算法, 参考下文。 这里先介绍v8是怎么做内存分代的。 新生代 v8中的新生代主要存放的是生存周期较短的对象, 它具有两个空间semispace, 分别为From和To, 在分配内存的时候将内存分配给From空间, 当垃圾回收的时候, 会检查From空间存活的对象(广度优先算法)并复制到To空间, 然后清空From空间, 再互相交换From和To空间的位置, 使得To空间变为From空间。 该算法缺陷很明显就是有一半的空间一直闲置着并且需要复制对象, 但是由于新生代本身具有的内存比较小加上其分配的对象都是生存周期比较短的对象, 所以浪费的空间以及复制使用的开销会比较小。 在64位系统中一个semisapce为16MB, 而32位则为8MB, 所以新生代内存大小分别为32MB和16MB。 老生代 老生代主要存放的是生存周期比较长的对象。内存按照 1MB 分页,并且都按照 1MB 对齐。新生代的内存页是连续的,而老生代的内存页是分散的,以链表的形式串联起来。 它的内部有4种类型。 ...

May 5, 2019 · 3 min · jiezi

PHP回收周期

原文:回收周期(Collecting Cycles) ,增加一篇论文在底部。以下过程仅对数组和对象类型起作用。传统上,像以前的 php 用到的引用计数内存机制,无法处理循环的引用内存泄漏。然而 5.3.0 PHP 使用文章» 引用计数系统中的同步周期回收(Concurrent Cycle Collection in Reference Counted Systems)中的同步算法,来处理这个内存泄漏问题。对算法的完全说明有点超出这部分内容的范围,将只介绍其中基础部分。首先,我们先要建立一些基本规则,如果一个引用计数增加,它将继续被使用,当然就不再在垃圾中。如果引用计数减少到零,所在变量容器将被清除(free)。就是说,仅仅在引用计数减少到非零值时,才会产生垃圾周期(garbage cycle)。其次,在一个垃圾周期中,通过检查引用计数是否减1,并且检查哪些变量容器的引用次数是零,来发现哪部分是垃圾。为避免不得不检查所有引用计数可能减少的垃圾周期,这个算法把所有可能根(possible roots 都是zval变量容器),放在根缓冲区(root buffer)中(用紫色来标记,称为疑似垃圾),这样可以同时确保每个可能的垃圾根(possible garbage root)在缓冲区中只出现一次。仅仅在根缓冲区满了时,才对缓冲区内部所有不同的变量容器执行垃圾回收操作。看上图的步骤 A。在步骤 B 中,模拟删除每个紫色变量。模拟删除时可能将不是紫色的普通变量引用数减”1″,如果某个普通变量引用计数变成0了,就对这个普通变量再做一次模拟删除。每个变量只能被模拟删除一次,模拟删除后标记为灰(原文说确保不会对同一个变量容器减两次”1″,不对的吧)。在步骤 C 中,模拟恢复每个紫色变量。恢复是有条件的,当变量的引用计数大于0时才对其做模拟恢复。同样每个变量只能恢复一次,恢复后标记为黑,基本就是步骤 B 的逆运算。这样剩下的一堆没能恢复的就是该删除的蓝色节点了,在步骤 D 中遍历出来真的删除掉。算法中都是模拟删除、模拟恢复、真的删除,都使用简单的遍历即可(最典型的深搜遍历)。复杂度为执行模拟操作的节点数正相关,不只是紫色的那些疑似垃圾变量。现在,你已经对这个算法有了基本了解,我们回头来看这个如何与PHP集成。默认的,PHP的垃圾回收机制是打开的,然后有个 php.ini 设置允许你修改它:zend.enable_gc 。当垃圾回收机制打开时,每当根缓存区存满时,就会执行上面描述的循环查找算法。根缓存区有固定的大小,可存10,000个可能根,当然你可以通过修改PHP源码文件Zend/zend_gc.c中的常量GC_ROOT_BUFFER_MAX_ENTRIES,然后重新编译PHP,来修改这个10,000值。当垃圾回收机制关闭时,循环查找算法永不执行,然而,可能根将一直存在根缓冲区中,不管在配置中垃圾回收机制是否激活。当垃圾回收机制关闭时,如果根缓冲区存满了可能根,更多的可能根显然不会被记录。那些没被记录的可能根,将不会被这个算法来分析处理。如果他们是循环引用周期的一部分,将永不能被清除进而导致内存泄漏。即使在垃圾回收机制不可用时,可能根也被记录的原因是,相对于每次找到可能根后检查垃圾回收机制是否打开而言,记录可能根的操作更快。不过垃圾回收和分析机制本身要耗不少时间。除了修改配置zend.enable_gc ,也能通过分别调用gc_enable() 和 gc_disable()函数来打开和关闭垃圾回收机制。调用这些函数,与修改配置项来打开或关闭垃圾回收机制的效果是一样的。即使在可能根缓冲区还没满时,也能强制执行周期回收。你能调用gc_collect_cycles()函数达到这个目的。这个函数将返回使用这个算法回收的周期数。允许打开和关闭垃圾回收机制并且允许自主的初始化的原因,是由于你的应用程序的某部分可能是高时效性的。在这种情况下,你可能不想使用垃圾回收机制。当然,对你的应用程序的某部分关闭垃圾回收机制,是在冒着可能内存泄漏的风险,因为一些可能根也许存不进有限的根缓冲区。因此,就在你调用gc_disable()函数释放内存之前,先调用gc_collect_cycles()函数可能比较明智。因为这将清除已存放在根缓冲区中的所有可能根,然后在垃圾回收机制被关闭时,可留下空缓冲区以有更多空间存储可能根。引用计数系统中的同步周期回收(ConcurrentCycleCollection)

February 27, 2019 · 1 min · jiezi

GO GC 垃圾回收机制

垃圾回收(Garbage Collection,简称GC)是编程语言中提供的内存管理功能。在传统的系统级编程语言(主要指C/C++)中,程序员定义了一个变量,就是在内存中开辟了一段相应的空间来存值。由于内存是有限的,所以当程序不再需要使用某个变量的时候,就需要销毁该对象并释放其所占用的内存资源,好重新利用这段空间。在C/C++中,释放无用变量内存空间的事情需要由程序员自己来处理。就是说当程序员认为变量没用了,就手动地释放其占用的内存。但是这样显然非常繁琐,如果有所遗漏,就可能造成资源浪费甚至内存泄露。当软件系统比较复杂,变量多的时候程序员往往就忘记释放内存或者在不该释放的时候释放内存了。这对于程序开发人员是一个比较头痛的问题。为了解决这个问题,后来开发出来的几乎所有新语言(java,python,php等等)都引入了语言层面的自动内存管理 – 也就是语言的使用者只用关注内存的申请而不必关心内存的释放,内存释放由虚拟机(virtual machine)或运行时(runtime)来自动进行管理。而这种对不再使用的内存资源进行自动回收的功能就被称为垃圾回收。垃圾回收常见的方法引用计数(reference counting)引用计数通过在对象上增加自己被引用的次数,被其他对象引用时加1,引用自己的对象被回收时减1,引用数为0的对象即为可以被回收的对象。这种算法在内存比较紧张和实时性比较高的系统中使用的比较广泛,如ios cocoa框架,php,python等。优点:1、方式简单,回收速度快。缺点:1、需要额外的空间存放计数。2、无法处理循环引用(如a.b=b;b.a=a这种情况)。3、频繁更新引用计数降低了性能。标记-清除(mark and sweep)该方法分为两步,标记从根变量开始迭代得遍历所有被引用的对象,对能够通过应用遍历访问到的对象都进行标记为“被引用”;标记完成后进行清除操作,对没有标记过的内存进行回收(回收同时可能伴有碎片整理操作)。这种方法解决了引用计数的不足,但是也有比较明显的问题:每次启动垃圾回收都会暂停当前所有的正常代码执行,回收是系统响应能力大大降低!当然后续也出现了很多mark&sweep算法的变种(如三色标记法)优化了这个问题。复制收集复制收集的方式只需要对对象进行一次扫描。准备一个「新的空间」,从根开始,对对象进行扫,如果存在对这个对象的引用,就把它复制到「新空间中」。一次扫描结束之后,所有存在于「新空间」的对象就是所有的非垃圾对象。这两种方式各有千秋,标记清除的方式节省内存但是两次扫描需要更多的时间,对于垃圾比例较小的情况占优势。复制收集更快速但是需要额外开辟一块用来复制的内存,对垃圾比例较大的情况占优势。特别的,复制收集有「局部性」的优点。在复制收集的过程中,会按照对象被引用的顺序将对象复制到新空间中。于是,关系较近的对象被放在距离较近的内存空间的可能性会提高,这叫做局部性。局部性高的情况下,内存缓存会更有效地运作,程序的性能会提高。对于标记清除,有一种标记-压缩算法的衍生算法:对于压缩阶段,它的工作就是移动所有的可达对象到堆内存的同一个区域中,使他们紧凑的排列在一起,从而将所有非可达对象释放出来的空闲内存都集中在一起,通过这样的方式来达到减少内存碎片的目的。分代收集(generation)这种收集方式用了程序的一种特性:大部分对象会从产生开始在很短的时间内变成垃圾,而存在的很长时间的对象往往都有较长的生命周期。根据对象的存活周期不同将内存划分为新生代和老年代,存活周期短的为新生代,存活周期长的为老年代。这样就可以根据每块内存的特点采用最适当的收集算法。 新创建的对象存放在称为 新生代(young generation)中(一般来说,新生代的大小会比 老年代小很多)。高频对新生成的对象进行回收,称为「小回收」,低频对所有对象回收,称为「大回收」。每一次「小回收」过后,就把存活下来的对象归为老年代,「小回收」的时候,遇到老年代直接跳过。大多数分代回收算法都采用的「复制收集」方法,因为小回收中垃圾的比例较大。这种方式存在一个问题:如果在某个新生代的对象中,存在「老生代」的对象对它的引用,它就不是垃圾了,那么怎么制止「小回收」对其回收呢?这里用到了一中叫做写屏障的方式。程序对所有涉及修改对象内容的地方进行保护,被称为「写屏障」(Write Barrier)。写屏障不仅用于分代收集,也用于其他GC算法中。在此算法的表现是,用一个记录集来记录从新生代到老生代的引用。如果有两个对象A和B,当对A的对象内容进行修改并加入B的引用时,如果①A是「老生代」②B是「新生代」。则将这个引用加入到记录集中。「小回收」的时候,因为记录集中有对B的引用,所以B不再是垃圾。三色标记算法三色标记算法是对标记阶段的改进,原理如下:起初所有对象都是白色。从根出发扫描所有可达对象,标记为灰色,放入待处理队列。从队列取出灰色对象,将其引用对象标记为灰色放入队列,自身标记为黑色。重复 3,直到灰色对象队列为空。此时白色对象即为垃圾,进行回收。可视化如下。三色标记的一个明显好处是能够让用户程序和 mark 并发的进行,具体可以参考论文:《On-the-fly garbage collection: an exercise in cooperation.》。Golang 的 GC 实现也是基于这篇论文,后面再具体说明。GO的垃圾回收器go语言垃圾回收总体采用的是经典的mark and sweep算法。v1.3以前版本 STW(Stop The World)golang的垃圾回收算法都非常简陋,然后其性能也广被诟病:go runtime在一定条件下(内存超过阈值或定期如2min),暂停所有任务的执行,进行mark&sweep操作,操作完成后启动所有任务的执行。在内存使用较多的场景下,go程序在进行垃圾回收时会发生非常明显的卡顿现象(Stop The World)。在对响应速度要求较高的后台服务进程中,这种延迟简直是不能忍受的!这个时期国内外很多在生产环境实践go语言的团队都或多或少踩过gc的坑。当时解决这个问题比较常用的方法是尽快控制自动分配内存的内存数量以减少gc负荷,同时采用手动管理内存的方法处理需要大量及高频分配内存的场景。v1.3 Mark STW, Sweep 并行1.3版本中,go runtime分离了mark和sweep操作,和以前一样,也是先暂停所有任务执行并启动mark,mark完成后马上就重新启动被暂停的任务了,而是让sweep任务和普通协程任务一样并行的和其他任务一起执行。如果运行在多核处理器上,go会试图将gc任务放到单独的核心上运行而尽量不影响业务代码的执行。go team自己的说法是减少了50%-70%的暂停时间。v1.5 三色标记法go 1.5正在实现的垃圾回收器是“非分代的、非移动的、并发的、三色的标记清除垃圾收集器”。引入了上文介绍的三色标记法,这种方法的mark操作是可以渐进执行的而不需每次都扫描整个内存空间,可以减少stop the world的时间。 由此可以看到,一路走来直到1.5版本,go的垃圾回收性能也是一直在提升,但是相对成熟的垃圾回收系统(如java jvm和javascript v8),go需要优化的路径还很长(但是相信未来一定是美好的~)。v1.8 混合写屏障(hybrid write barrier)这个版本的 GC 代码相比之前改动还是挺大的,采用一种混合的 write barrier 方式 (Yuasa-style deletion write barrier [Yuasa ‘90] 和 Dijkstra-style insertion write barrier [Dijkstra ‘78])来避免 堆栈重新扫描。混合屏障的优势在于它允许堆栈扫描永久地使堆栈变黑(没有STW并且没有写入堆栈的障碍),这完全消除了堆栈重新扫描的需要,从而消除了对堆栈屏障的需求。重新扫描列表。特别是堆栈障碍在整个运行时引入了显着的复杂性,并且干扰了来自外部工具(如GDB和基于内核的分析器)的堆栈遍历。此外,与Dijkstra风格的写屏障一样,混合屏障不需要读屏障,因此指针读取是常规的内存读取; 它确保了进步,因为物体单调地从白色到灰色再到黑色。混合屏障的缺点很小。它可能会导致更多的浮动垃圾,因为它会在标记阶段的任何时刻保留从根(堆栈除外)可到达的所有内容。然而,在实践中,当前的Dijkstra障碍可能几乎保留不变。混合屏障还禁止某些优化:特别是,如果Go编译器可以静态地显示指针是nil,则Go编译器当前省略写屏障,但是在这种情况下混合屏障需要写屏障。这可能会略微增加二进制大小。小结:通过go team多年对gc的不断改进和忧化,GC的卡顿问题在1.8 版本基本上可以做到 1 毫秒以下的 GC 级别。 实际上,gc低延迟是有代价的,其中最大的是吞吐量的下降。由于需要实现并行处理,线程间同步和多余的数据生成复制都会占用实际逻辑业务代码运行的时间。GHC的全局停止GC对于实现高吞吐量来说是十分合适的,而Go则更擅长与低延迟。 并行GC的第二个代价是不可预测的堆空间扩大。程序在GC的运行期间仍能不断分配任意大小的堆空间,因此我们需要在到达最大的堆空间之前实行一次GC,但是过早实行GC会造成不必要的GC扫描,这也是需要衡量利弊的。因此在使用Go时,需要自行保证程序有足够的内存空间。垃圾收集是一个难题,没有所谓十全十美的方案,通常是为了适应应用场景做出的一种取舍。相信GO未来会更好。参考:https://github.com/golang/pro…http://legendtkl.com/2017/04/...https://blog.twitch.tv/gos-ma...https://blog.plan99.net/moder...links目录 ...

February 15, 2019 · 1 min · jiezi

垃圾回收机制

1、 引用计数 引用计数算法是垃圾回收最早的算法,有其优势,也有其劣势,但是现在已经很少有使用了。原理:为每个对象添加一个计数器,表示对象的引用次数,每当创建一个新的引用指向该对象时其计数器就加1,每当指向该对象的引用失效时计数器就减1。当计数器的值为0时就会被浏览器回收。优点:可即刻回收垃圾:当计数器值为0时,会马上回收对象,提高内存使用效率STW(Stop-The-World)短: 回收垃圾时,浏览器会停止响应其他操作,引用计数不需要遍历堆,有效减少STW时间缺点:计数器的增减操作频繁计数器需要占用一定的内存实现繁琐,更新引用时很容易导致内存泄露。循环引用无法回收(最重要的缺点)2、 标记清除 现代浏览器使用最多的一种垃圾回收机制。原理:从名字就可以看出,该算法分为两个步骤:1.标记所有可访问的对象;2.清除所有未被标记的对象。优点:算法实现简单。缺点:碎片化:清楚后的空间是不连续的,不利于后面空间的分配。分配速度慢:由于空间碎片化,所以每次分配需要遍历空闲链表。STW(Stop-The-World)长:两个阶段均要遍历整个堆。优化:采用标记整理算法或标记压缩算法进行内存整理,避免内存碎片化。3、 GC优化策略 - 分代回收 V8 实现了准确式 GC,采用了分代式垃圾回收机制,将内存(堆)按照8:2分为老生代和新生代两部分,其中新生代又按照1:1分为了两块Survivor空间。 一方面新生代中的对象多是临时对象,另一方面新生代空间中有一半是空闲的,所以只分的两成的空间。 新生代 GC采用复制算法。在新生代空间中,内存空间分为两部分,分别为 From 空间和 To 空间。在这两个空间中,必定有一个空间是使用的,另一个空间是空闲的。新分配的对象会被放入 From 空间中,当 From 空间被占满时,新生代 GC 就会启动了。算法会检查 From 空间中存活的对象并复制到 To 空间中,如果有失活的对象就会销毁。当复制完成后将 From 空间和 To 空间互换,这样 GC 就结束了。如果垃圾回收时,存活的对象太多,To 空间的对象占比大小超过 25 %。在这种情况下,为了不影响到内存分配,会将对象从新生代空间移到老生代空间中,就会出现有些对象提前进入老生代的情况。 当新生代中的对象经历过一次 Scavenge 算法后,会将对象从新生代空间移到老生代空间中。 老生代 GC采用标记清除算法和标记压缩算法。老年代中主要存放存活时间较长的持久对象,这类对象数量较多。当老年代中的内存消耗超过一定限制时,会先启动标记清除算法,清除对象后会造成堆内存出现碎片的情况,当碎片超过一定限制后会启动压缩算法。在压缩过程中,将活的对象像一端移动,直到所有对象都移动完成然后清理掉不需要的内存。 在 GC时,浏览器会停止响应其他操作,而一次GC可能需要几百毫秒才能完成。这就会导致一些性能上的问题。为了解决这个问题,2011 年,V8 从 stop-the-world(STW) 标记切换到增量标志。在增量标记期间,GC 将标记工作分解为更小的模块,可以让 JS 应用逻辑在模块间隙执行一会,从而不至于让应用出现停顿情况。在 2018 年,GC 技术又有了一个重大突破,这项技术名为并发标记。该技术可以让 GC 扫描和标记对象时,同时允许 JS 运行。

January 2, 2019 · 1 min · jiezi