凯发k8官网-凯发k8官网登录
当前位置:主页 > 互联网大会 >

直击面试,聊聊 GC 机制

发表日期:2020-01-01 19:04文章编辑:admin浏览次数: 标签:    

文章来历: https://studyidea.cn/

GC 中文直译废物收回,是一种收回内存空间防止内存走漏的机制。当 JVM 内存严重,经过履行 GC 有用收回内存,转而分配给新目标然后完成内存的再运用。 JVM GC 机制尽管无需开发主动参加,减轻不少作业量,可是某些状况下,主动 GC 将会导致体系功用下降,呼应变慢,所以这就需求咱们提早了解把握 GC 机制。当面临这种状况时,才干镇定自若的处理问题。别的 GC 机制也是 Java 面试高频考题,了解把握 GC 是一项必备技术。

学习 GC ,首要咱们处理三个问题:

咱们先来看一段简略的代码。

上面代码经过将字符串目标转化成字节数组,然后写入本地文件。办法一旦开端履行,就将会在分配必定内存给新建的目标,然后将引证告知了 str , bytes 变量。比及办法履行结束,办法内部局部变量紧接迁就会被毁掉。可是这样仅仅毁掉了局部变量,却没有带走内存上这些实践的目标。这类不再起作用,没有被引证的目标,将其归类为废物。

在偌大的内存上存活着许多目标, GC 之前需求精确将这些目标符号出来,分为存活目标与废物目标。这个进程一旦少符号,那就只能等候下次 GC 符号,再收回,这样将会影响 GC 功率。别的决不能错符号,将正常存活目标符号为废物。一旦收回正常存活的目标,或许就会引起程序各种溃散。

现在有两种算法能够用来符号:

引证计数法经过在目标头分配一个字段,用来存储该目标引证计数。一旦该目标被其他目标引证,计数加 1。假如这个引证失效,计数减 1。当引证计数值为 0 时,代表这个目标已不再被引证,能够被收回。

如上图所示,当 str 引证堆中目标时,计数值添加为 1。当 str 变为 null 时,既不再引证该目标,计数值减 1。此刻该目标就能够被 GC 收回。

引证计数法只需求判别计数值,所以完成比较简略,这个进程也比较高效。可是存在一个很严重的问题,无法处理目标循环引证问题。

从上图能够看到, a , b 不再引证堆中目标,导致计数减一。此刻两个目标内部还存在相互引证,计数值不为 0,此刻 GC 没办法收回该目标。

这个算法首要需求依照规矩查找当时活泼的引证,将其称为 GC Roots 。接着将 GC Roots 作为根节点动身,遍历目标引证联系图,将能够遍历的目标符号为存活,其他目标作为无用目标。

留意这儿是是 引证 ,而不是目标。

从上图能够看到,绿色目标尽管存在循环引证,可是因为这些目标不能被 GC Roots 遍历到,所以将会被收回。

能够被作为 GC Roots 活泼引证包含但不限于以下引证:

还记得刚开端触摸 Java 时,只知道仓库,目标实例分配在堆中,办法中局部变量坐落栈中。实践上 JVM 内存区域区分愈加详尽,分为:

如图所示,咱们将内存区分为线程私有与线程同享的区域。办法区与堆都是线程同享的区域,这两部分占用 JVM 大部分内存,剩下三个小弟将会跟线程绑定,跟着线程消亡,主动将会被 JVM 收回。

堆应该是咱们最了解的一块区域,几乎一切目标实例都将会在此出世,一般也是虚拟机上占用内存最大一块区域,几乎便是 JVM 内存中的大哥大。堆内存内部也不是简简略单一块罢了,现在将会依据分代算法,将堆分代,不同目标坐落不同区域。这一点咱们下文再具体了解。

办法区将会保存已被虚拟上加载的类信息、常量,静态变量,字节码等信息,堆上的目标正式经过办法区这些信息,才干正确创立出来。

虚拟机栈栈由一系列栈帧组成,每个栈帧其实代表一个办法,栈帧中将会保存一个办法的局部变量表,办法出入口信息,操作栈等。每逢调用一个办法,就将会把这个栈帧压入栈中,履行结束,出栈。

本地办法栈与虚拟机栈比较相似,最大差异在于,虚拟机栈履行的 Java 办法,而本地办法栈将会用来履行 Native 办法服务。下面办法就会在本地办法栈中履行。

public static native void arraycopy;

程序核算器能够说是这几块区域占用最小的一部分,可是功用却十分重要。Java 源代码经过编译变成字节码,然后被 JVM 载入运转之后,将会变成一条条指令,而程序计数器的作业便是告知当时线程下一条需求履行指令。这样即便发生了线程切换,等候康复的时分,当时线程仍然知道接下去要履行的指令。

现在干流 GC 算法首要分为三种:

这是一个最为根底也是最简略完成的算法,首要完成进程分为两步:符号,铲除。

ps:这个图着实难画啊。。。。

能够看到经过这个算法收回之后,尽管堆空间被收拾出来,可是也发生许多 空间碎片 。这就会导致一个新目标依据堆剩下容量核算,看起来是能够分配,可是实践分配进程,因为没有接连内存,导致虚拟机感知到内存缺乏,又不得不提早再次触发 GC 。

或许这儿你就会有疑问,为什么目标需求分配一块接连的内存?

这儿引证一下 R 神 @RednaxelaFX 答案。

别的这个算法还有一个缺乏:符号与铲除功率比较低。这就竟会导致 GC 占用时刻过长,影响正常程序运用。

为了处理上述功率问题,诞生仿制算法。这个算法将可用内存分为两块,每次只运用其间一块,当这一块内存运用结束,触发 GC ,将会把存活的目标顺次仿制到别的一块上,然后再把已运用过的内存一次性收拾。

这个算法每次只需求操作一半内存, GC 收回之后也不存在任何空间碎片,新目标内存分配时只需求移动堆顶指针,按次序分配内存即可,完成简略,运转高效。可是这个算法搁置一半内存空间,空间运用功率不高。

PS:仿制算法以空间换时刻,两者不行兼得

别的目标存活率也会影响仿制算法功率。假如目标大部分都是朝生夕死,只需求移动少数存活目标,就能腾出大部分空间。反而假如目标存活率高,这就需求进行较多的仿制操作,收回之后也并没有剩下内存,这就或许导致频频触发 GC 。

针对这种存活时刻长的目标,就需求运用符号-收拾算法。

符号-收拾算法能够说是符号-铲除算法的改善版,改善了铲除导致的空间碎片问题。这个算法分为两步:

GC Roots

尽管符号-收拾算法处理了符号-铲除算法空间碎片问题,也完好运用整个内存空间,可是这个算法问题功率并不高。相较于符号-铲除算法,符号-收拾算法多添加收拾这一步,所以该算法功率还低于符号-铲除算法。

从上面三种 GC 算法能够看到,并没有一种空间与时刻功率都是比较完美的算法,所以只能做的是综合运用各种算法特色将其作用到不必的内存区域。

现在商业虚拟机依据目标存活周期不同区分内存区域,一般分为新生代,老时代。新目标一般状况都会优先分配在新生代,新生代目标若存活时刻大于必定阈值之后,将会移到至老时代。新生代的目标都是短寿鬼,老时代的目标都是长命先生。

新生代每次 GC 之后都能够收回大批量目标,所以比较合适仿制算法,只需求支付少数仿制存活目标的本钱。这儿内存区分并没有依照 1:1 区分,默许将会依照 8:1:1 区分红 Eden 与两块 Survivor 空间。每次运用 Eden 与一块 Survivor 空间,这样咱们仅仅搁置 10% 内存空间。不过咱们每次收回并不能确保存活目标小于 10%,在这种状况下就需求依托老时代的内存分配担保。当 Survivor 空间并不能保存剩下存活目标,就将这些目标经过分配担保进制移动至老时代。

老时代中目标存活率将会特别高,且没有额定空间进行分配担保,所以并不合适仿制算法,所以需求运用符号-铲除或符号-收拾算法。

最近又到一年一次大考的时分,不得不又拿起周志明『深化 Java 虚拟机』从头学习。还记得第一次翻看这本书的时分,多半内容看不懂,看完也很快就忘了。然后过了一段时刻,又从头拿起此书,这次比前次好,也现已能看小多半了。最近跟一些小伙伴谈天,发现他们都是看这本书学习 JVM ,不得不说这本书真是一本神书。最近『深化 Java 虚拟机』第三版即将上架开售,有需求的小伙伴能够考虑下手了。

好了 , GC 机制就就总结到这儿,下一篇咱们来聊聊 JVM 常用 GC 收回器。

GC Roots

Java虚拟机详解04----GC算法和品种

深化 Java 虚拟机

欢迎重视我的大众号:程序通事,取得日常干货推送。假如您对我的专题内容感兴趣,也能够重视我的博客: studyidea.cn

返回列表
  • 上一篇:没有了
  • 下一篇:没有了
相关新闻