Java-虚拟机-垃圾收集器/垃圾收集算法/GCROOT根

网友投稿 253 2022-09-23

Java-虚拟机-垃圾收集器/垃圾收集算法/GCROOT根

本文应该与​​堆的内存规划​​合二为一,不过还不知道如何排版,所以目前就先这样子吧

概念:STW,stop the word,指的是当前我们自己的应用线程暂停,但是虚拟机的GC线程依然运行

垃圾回收算法

1.引用计数法

首先,每个对象都有一个与之对应的引用计数器,当其他对象引用该对象的时候,计数器+1,当不再引用该对象时,计数器-1,当引用计数器为0的时候,此时该对象不会再次被引用,等待着被收回的命运,由于引用计数法无法处理循环引用,所以JVM中只有Symbol Table中使用了该算法 Symbol Table:每个常量池都含有字符串,为了节省空间,这些字符串都是存放在一个叫做Symbol Table的内存区域,如果两个类中都有"abc"的字符串,则在Symbol Table中只包含一个"abc",并且被标记引用计数=2,当这两个类都被卸载的时候,"abc"才被删除,而不需要进行可达性分析

2.标记清除法

该算法分为两个阶段,标记阶段和清除阶段

2.1通过根节点,标记所有能通过根节点可以到达的对象,此时没被标记的就是垃圾对象,下图绿色表示有用的对象,灰色表示没用的对象(将被回收)

2.2垃圾回收时清除所有没被标记的对象

下图灰色区域表示已经空闲下来的内存空间

从第二幅图中明显可以看出,回收之后,内存空间并不是连续的,也就是说空间碎片很多,这也是标记清除法的缺点3.标记压缩法(也叫标记整理法)

如果你玩英雄联盟,那么这个算法特别好理解,因为和纳尔的大招是一样的

该算法克服了上述标记清除法有空间碎片的缺点,它分三个阶段(就是比标记清除法多出一个阶段)

1.标记阶段(和标记清除法相同,可回去看图)

2.清除阶段(和标记清除法相同,可回去看图)

3.将所有存活压缩到内存的另一端(纳尔放大招,直接推到墙上),如下面两幅图所示

当清除阶段完毕之后,压缩之前,内存中是这个样子的

压缩之后,内存中是下面这个样子的,已经没有空间碎片了

标记压缩法适合老年代,相对来说,老年代区绝大多数的对象都是存活的,和复制算法相比,标记压缩法适合存活的对象占据大多数情况,不方便移动到另一个区域,因为老人行动不便…

4.复制算法 该算法的思想是将内存分为两部分A和B,A部分有对象,B部分没有对象,在垃圾回收开始的时候,将A中所有存活对象挪到B中,然后清空A内存中所有对象,此时,A内存空下来,也不会产生碎片的问题,如此往复循环,AB互换,这就是复制算法

复制算法的缺点是将内存减半了,它适合内存中垃圾对象相对较多的情况,因为相对来说,垃圾对象越多,有用对象就越少,这样,挪动的对象就越少,所以该算法适合新生代内存区,因为年轻人爱运动…

下图中表示了复制算法在回收之前AB两部分内存的使用情况

(其中绿色表示活着的对象,对象a-对象k表示垃圾对象)

下图表示垃圾回收之后,AB两部分内存的使用情况

注意:上文图中,出现了根对象,这指的是什么???根对象指的下面3种

1.如果一个对象,当前在某个线程的栈帧中引用它,那么该对象就是根对象 2.如果一个对象,在JVM的方法区中有引用它,那么该对象就是根对象 3.如果一个对象,有用native(JNI)修饰的方法在引用他,那么它就是根对象

这正好解决了内存中,两个对象循环引用的问题

安全点safe point

当执行GC的时候,不是立马就执行的,而是需要一个恰当的时机,这个时机,就叫做安全点,可以理解和坐公交一样,不是说你想下车,就马上下车的,最起码,也要等车到达最近一个公交站点,才允许下车,下面几个位置(时机)可以作为安全点​​​方法返回之前​​​​调用某个方法之后​​​​抛出异常的位置​​​​循环的末尾​​ 主要是为了保证代码执行的完整度 safepoint逻辑上等同于CyclicBarrie,当要执行GC的时候,安全点打开,所以所有线程都会在执行上述几个位置的时候,进入安全点表,所有线程都跑到安全点之后,GC线程开始执行GC

安全区域safe region

如果一段代码在运行的时候,引用关系不会发生变化,那么在这段代码就是安全区域,而安全区域里的任意位置,都是安全点,比如下面这段代码

Thread.sleep(666);

sleep方法中的任意位置,都是安全点,所以GC的时候,如果某个线程正好处于安全区域,则该线程相当于直接到达了安全点

垃圾收集器

1.Serial 串行垃圾收集器,在垃圾回收阶段(STW阶段)只有一个线程负责垃圾回收,简单而高效,但是现在基本都是多核CPU,不能很好利用多核特性,使用下面命令开启Serial垃圾收集器

// 在新生代使用Serial收集器,采用复制算法-XX:+UseSerialGC// 在老年代使用Serial收集器,采用标记压缩法-XX:+UseSerialOldGC

2.ParNew 与Serial一样,不同点是用来匹配多核CPU的,在垃圾回收阶段(STW阶段)可以多个线程同时垃圾回收,也可以使用-XX:ParallelGCThreads来指定线程数,不过不推荐修改,默认就可以,使用下面命令开启ParNew垃圾收集器

// 该垃圾收集器只适用于年轻代,不能用于老年代-XX:+UseParNewGC

3.Parallel Scavenge 这个收集器不太了解,我个人理解是应该在时间上可以自由分配,比方说我分配每次GC回收50毫秒,那么这个收集器会在50好秒内尽可能的回收垃圾对象,说白了就是这么长时间内,你能干多少,就干多少,干不完就算了,下次再干, 有点像G1,具体不清楚,待以后研究hotspot的时候再补上

// 在新生代使用Parallel Scavenge收集器,采用复制算法-XX:+UseParallelGC// 在老年代使用Parallel Scavenge收集器,采用标记压缩法-XX:+UseParallelOldGC

4.CMS(Concurrent Mark Sweep) 翻译成中文是并发标记清理,由此可见该收集器是标记清除算法的实现,该收集器的最终目的是STW时间越短越好,且该收集器只用于老年代,具体步骤如下

(1)初始标记阶段,首先STW,然后所有标记root对象,注意,只标记root对象(标记谁就保留谁,没被标记的将会被回收) (2)并发标记阶段,不会STW,并且标记所有root对象所引用的对象,也就是说顺着root对象找到整个对象引用链,这个过程是CMS收集器最耗时的过程,大约占百分之七八十的时间 笔记:并发两个字指的是找链过程和我们的应用程序,同时执行,所以有可能会出现对象消失,或者浮动垃圾 (2.1)对象消失:标记过的对象,引用了没有被标记的对象,例如引用链A->B->C->D,当GC标记完B,还没有标记C对象的时候,恰好应用程序将A引用D,C与D断开,此时,D将无法被标记 (2.2)浮动垃圾:假设程序原本C引用D,GC标记完之后,C断开D的引用,D成了新的垃圾 (3)重新标记阶段,会STW,该阶段会修正上一个阶段产生的对象消失这种情况 (3.1)修正对象消失:当对上个阶段对A进行增加引用的时候,如果A已经被标记过,那么将记录A(使用​​​卡表​​​),本阶段将A作为Root根,重新标记一边,以此方式来修正对象消失,也就是说上个阶段修改了对象引用链,那么这个阶段就要重新标记一下这段链 笔记:就是因为2和3两个阶段,导致CMS垃圾收集器只用于老年代,因为相对来说,老年代对象比较稳定,修正错误的情况相对较少,如果年轻代使用CMS,那么得一直修正错误 (4)并发清理阶段,不会STW,清除所有没被标记的对象

CMS的缺点

缺点1浮动垃圾下次GC的时候会清除,个人不认为这是缺点 缺点2会占用CPU资源,个人不认为这是缺点 缺点3默认会产生内存碎片,但是可以通过-XX:+UseCMSCompactAtFullCollection参数让jvm执行完毕之后进行一次压缩(整理),这样就不会产生碎片,但是会耗时,这是需要权衡的

CMS转换Serial(重要) 当CMS的2和4阶段进行时,如果产生full GC(比如老年代满了),则直接STW,之后,JVM会使用Serial垃圾收集器进行回收垃圾,因为这个过程很麻烦,所以采用串行是最简单的方式 笔记:这种情况也叫concurrent mode failure

下面的命令是CMS垃圾收集器常用命令,足够用

//在老年代使用CMS收集器,且该收集器只能用于老年代,不能用于年轻代-XX:+UseConcMarkSweepGC

// 并发的GC线程数-XX:ConcGCThreads

// GC之后进行压缩整理-XX:+UseCMSCompactAtFullCollection

// GC多少次之后再压缩,默认0,表示每次GC都压缩带上// 注意使用该参数时必须带-XX:+UseCMSCompactAtFullCollection参数-XX:CMSFullGCsBeforeCompaction

// 当老年代对象第一次占老年代整个区域百分之多少的时候再回收// 默认92,表示百分之92-XX:CMSInitiatingOccupancyFraction

笔记:第一次会按照我们设定的这个值,之后GC的时候,不一定是按照我们设定的这个值,因为JVM内部会做一些优化,从而导致在我们设定的这个值上有浮动,比如我设定70,那么第一次达到70的时候会GC,但是第二次GC的时候不一定是70,有可能80,也有可能90,如果想要每次都按照70,则使用下面这个参数

// 需要配合上面的命令-XX:+UseCMSInitatingOccupancyOnly

笔记:XX:CMSInitiatingOccupancyFraction参数越小,则表示GC时间越短,但是GC越频繁,这是需要权衡的

// 在full GC之前进行一次minor GC,目的是减少老年代对年轻代的引用-XX:+CMSScavengeBeforeRemark

笔记:这个参数我持怀疑态度,按理说无论怎么minor GC,都不影响老年代对年轻代的引用,比如老年代有1个对象,引用了年轻代10个对象,无论如何minor GC,只要老年代这个对象存在,则年轻代这10个不是依然存在吗??而且是顺着老年代对象链查找的,也不会减少轮询次数,所以这个参数是我理解错了???

5.G1

与CMS不同,G1即可回收年轻代,又可回收老年代,而CMS只能用于老年代

G1将堆话分成多个大小相等的独立区域Region,最多可有2048个Region, 假设给堆分配4096M内存,则每个region占2M,下面的例子都会认为regison是2M 可以用​​​-XX:G1HeapRegionSize​​指定每个Region大小,但是不推荐自己指定

region有四种类型,每个region如果对象清空了,那么这个regison下次有可能是任意类型

(1)Eden:eden+surivor默认占比整个堆的5%,但是如果满了可能会触发GC(Young GC),也可能会拓容,最多能拓容到60%,每次回收之前都会计算时间成本,如果时间成本特别低,则拓容,如果时间成本接近筛选回收的值,则GC (2)Survior:和Ende的比例同之前一样,也是1:8 (3)Old:老年代 (4)Humongous大对象区,如果一个对象超过了regison大小的50%,本例中是1M,那么就是大对象,但是如果 分配的对象是5M,那么G1就会使用连续的三个Humongous来存放这个5M的对象,并没有放到老年代 (5)还有一些空region区

GC阶段

(1)初始标记:与CMS相同 (2)并发标记:与CMS相同,同时估算回收成本的时间权重,后续回收的时候会根据这个时间权重来排序 (3)最终标记:与CMS重新标记相同 (4)筛选回收:首先对每个Region的回收价值和成本进行排序(RSet活跃度,默认百分之85,-XX:G1MixedGCLiveThresholdPercent,超过该活跃度会被回收),根据用户指定的GC回收时间来回收,默认是200ms 可以通过​​​-XX:MaxGCPauseMillis​​来指定回收持续多长时间

G1采用的算法以及优化:

G1采用复制算法,那么就会出现多个region合并的情况,比方说region1和regison2中GC之后总大小是1.5M,那么G1会将region1和regison2存活的对象都复制到同一个region中,然后清空regison1和regison2

G1独有的相关参数​​​-XX:InitiatingHeapOccupancyPercent​​ 老年代占用的regison数量达到百分之多少(默认45%),就触发混合收集(MixedGC),混合收集指的是收集所有的Young区域,部分Old区域,部分Humongous区域,"部分"两个字由筛选回收阶段决定,注意,G1不到万不得已是不会触发Full GC的,这也是G1的初衷​​-XX:G1MixedGCLiveThresholdPercent​​ 每个region中存活的对象低于多少的时候,才会回收该region,注意是每个region中​​-XX:G1HeapWastePercent​​ 如果空出来的region达到百分之多少,就停止MixedGC,默认5%,优先级高于MaxGCPauseMillis

G1的Full GC 当MixedGC的复制算法没有region可用的时候,会出发full gc,原理同CMS,直接STW,开启Serial垃圾收集器开始单线程收集,非常耗时

G1使用场景 50%以上的堆都是存活的对象:此时如果其他收集器,存活对象多,则说明老年代对象就多,侧面说明老年代容易满,进一步导致频繁出发full GC 垃圾回收时间较长:如果使用其他收集器,回收时间比如超过1秒,那么体验就很不好,而G1的Mixed GC恰好可以指定时间 对STW停顿时间有要求的场景:原理同上 建议8G以上的堆内存:对于大内存,适合用G1,因为大内存满了之后其他收集器full GC的时候很消耗时间,而G1可以回收一会,运行一会,回收一会运行一会,体验更好

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:李亚鹏4000万债案再审开庭:“下跪”语音是恳求还是被胁迫?
下一篇:[LeetCode] Path Sum 二叉树的路径和
相关文章

 发表评论

暂时没有评论,来抢沙发吧~