c语言一维数组怎么快速排列
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小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~