旷野徒奔-Java底层篇(1.2)

1、JVM

→ 垃圾回收

GC 算法:标记清除、引用计数、复制、标记压缩、分代回收、

标记清除:标记清除法是现在GC算法的基础,目前似乎没有哪个GC还在使用这种算法了。因为这种算法会产生大量的内存碎片。

标记清除算法的执行过程分为两个阶段:标记阶段、清除阶段。

  • 标记阶段会通过可达性分析将不可达的对象标记出来。
  • 清除阶段会将标记阶段标记的垃圾对象清除。
    标记清除
    Java堆中,黄色对象为不可达对象,在标记阶段被标记。然后执行回收被标记的对象。
    在这里插入图片描述

可见回收后会产生大量不连续的内存空间,即内存碎片。由于Java在分配内存时通常是按连续内存分配,那么当碎片空间不足以分配给新的对象时,就造成了内存浪费。

引用计数
使用引用计数法,要先给每一个对象中添加一个计数器,一旦有地方引用了此对象,则该对象的计数器加1,如果引用失效了,则计数器减1。这样当计数器为0时,就代表此对象没有被任何地方引用。这种方法实现简单,判定效率也很高,在大部分情况下都是一个比较不错的方法。但是在Java虚拟机中并没有选用引用计数法来管理内存,其主要原因是它很难解决对象之间相互引用的问题,如果两个对应互相引用,导致他们的引用计数都不为0,最终不能回收他们。

复制算法
复制算法会将内存空间分为两块,每次只使用其中一块内存。复制算法同样使用可达性分析法标记除垃圾对象,当GC执行时,会将非垃圾对象复制到另一块内存空间中,并且保证内存上的连续性,然后直接清空之前使用的内存空间。然后如此往复。

如下图所示,r1和r2作为GC Root对象,经过可达性分析后,标记除黄色对象为垃圾对象。
在这里插入图片描述
复制过程如下,GC会将五个存活对象复制到to区,并且保证在to区内存空间上的连续性。
在这里插入图片描述
最后,将from区中的垃圾对象清除。由此可见,该算法在存货对象少,垃圾对象多的情况下,非常高效。其好处是不会产生内存碎片,但坏处也是显而易见的,就是直接损失了一半的可用内存。

标记压缩算法
标记压缩算法可以解决标记清除算法的内存碎片问题。
其算法可以看作三步:

  • 标记垃圾对象
  • 清除垃圾对象
  • 内存碎片整理
    第三步内存碎片整理:
    在这里插入图片描述

分代回收
分代算法基于复制算法和标记压缩算法。

标记清除算法会产生大量的内存碎片,复制算法会损失一半的内存,标记压缩算法的碎片整理会造成较大的消耗。

但是都有各自适合的场景:
1、复制算法适用于每次回收时,存活对象少的场景,这样就会减少复制量。
2、标记压缩算法适用于回收时,存活对象多的场景,这样就会减少内存碎片的产生,碎片整理的代价就会小很多。

分代算法将内存区域分为两部分:新生代和老年代。根据新生代和老年代中对象的不同特点,使用不同的GC算法。

新生代对象的特点是:创建出来没多久就可以被回收(例如虚拟机栈中创建的对象,方法出栈就会销毁)。也就是说,每次回收时,大部分是垃圾对象,所以新生代适用于复制算法。

老年代的特点是:经过多次GC,依然存活。也就是说,每次GC时,大部分是存活对象,所以老年代适用于标记压缩算法。

新生代分为eden区、from区、to区,老年代是一整块内存空间,如下所示:
在这里插入图片描述
分代算法执行过程
首先简述一下新生代GC的整个过程(老年代GC会在下面介绍):新创建的对象总是在eden区中出生,当eden区满时,会触发Minor GC,此时会将eden区中的存活对象复制到from和to中一个没有被使用的空间中,假设是to区(正在被使用的from区中的存活对象也会被复制到to区中)。

有几种情况,对象会晋升到老年代:

  • 超大对象会直接进入到老年代(受虚拟机参数-XX:PretenureSizeThreshold参数影响,默认值0,即不开启,单位为Byte,例如:3145728=3M,那么超过3M的对象,会直接晋升老年代)
  • 如果to区已满,多出来的对象也会直接晋升老年代
  • 复制15次(15岁)后,依然存活的对象,也会进入老年代
    此时eden区和from区都是垃圾对象,可以直接清除。

PS:为什么复制15次(15岁)后,被判定为高龄对象,晋升到老年代呢?
因为每个对象的年龄是存在对象头中的,对象头用4bit存储了这个年龄数,而4bit最大可以表示十进制的15,所以是15岁。

总结:
垃圾回收的整体思路分两个流派
引用计数: 就是上面说的第二种
可达性: 就是标记清除那种, 判断一个对象是否可以到达.
引用计数的最大优势应该就是不需要暂停程序去进行回收了, 随使用随回收. 但劣势也很明显: 需要计数器额外空间以及循环引用的问题.

【参考链接】https://blog.csdn.net/xzm_rainbow/article/details/84997446
https://www.cnblogs.com/hujingnb/p/12638443.html

对象存活的判定、垃圾收集器(CMS、G1、ZGC、Epsilon)、GC 参数

对象存活的判定
判断对象是否存活一般有两种方式:
引用计数:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收。此方法简单,缺点是无法解决对象相互循环引用的问题。
可达性分析(Reachability Analysis):从GC Roots开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。不可达对象。

垃圾收集器

CMS:CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。目前很大一部分的Java应用都集中在互联网站或B/S系统的服务端上,这类应用尤其重视服务的响应速度,希望系统停顿时间最短,以给用户带来较好的体验。

CMS收集器是基于“标记—清除”算法实现的,它的运作过程相对于前面几种收集器来说更复杂一些,整个过程分为4个步骤:

初始标记(CMS initial mark):初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快,需要“Stop The World”。
并发标记(CMS concurrent mark):并发标记阶段就是进行GC Roots Tracing的过程.
重新标记(CMS remark):重新标记阶段是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短,仍然需要“Stop The World”。
并发清除(CMS concurrent sweep):并发清除阶段会清除对象。

其中初始标记、重新标记这两个步骤仍然需要“Stop The World”。初始标记仅仅只是标记一下GC Roots能直接关联到的对象,速度很快,并发标记阶段就是进行GC Roots Tracing的过程,而重新标记阶段则是为了修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段稍长一些,但远比并发标记的时间短。

优点:并发收集、低停顿

缺点:产生大量空间碎片、并发阶段会降低吞吐量、无法处理浮动垃圾(清理阶段,用户线程会产生新的垃圾,无法清理)

参数控制:-XX:+UseConcMarkSweepGC 使用CMS收集器
-XX:+ UseCMSCompactAtFullCollection Full GC后,进行一次碎片整理;整理过程是独占的,会引起停顿时间变长
-XX:+CMSFullGCsBeforeCompaction 设置进行几次Full GC后,进行一次碎片整理
-XX:ParallelCMSThreads 设定CMS的线程数量(一般情况约等于可用CPU数量)

G1:G1是目前技术发展的最前沿成果之一,HotSpot开发团队赋予它的使命是未来可以替换掉JDK1.5中发布的CMS收集器。与CMS收集器相比G1收集器有以下特点:

  • 并行与并发:G1能充分利用多CPU、多核环境下的硬件优势,使用多个CPU来缩短Stop-The-World停顿的时间,部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍然可以通过并发的方式让Java程序继续执行。
  • 分代收集:与其他收集器一样,分代概念在G1中依然得以保留。虽然G1可以不需要其他收集器配合就能独立管理整个GC堆,但它能够采用不同的方式去处理新创建的对象和已经存活了一段时间、熬过多次GC的旧对象以获取更好的收集效果。
  • 空间整合:与CMS的“标记—清理”算法不同,G1从整体来看是基于“标记—整理”算法实现的收集器,从局部(两个Region之间)上来看是基于“复制”算法实现的,但无论如何,这两种算法都意味着G1运作期间不会产生内存空间碎片,收集后能提供规整的可用内存。这种特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前触发下一次GC。
  • 可预测的停顿:这是G1相对于CMS的另一大优势,降低停顿时间是G1和CMS共同的关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒。

G1之前的其他收集器进行收集的范围都是整个新生代或者老年代,而G1不再是这样。使用G1收集器时,Java堆的内存布局就与其他收集器有很大差别,它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分Region(不需要连续)的集合。

收集步骤:
1、标记阶段,首先初始标记(Initial-Mark),这个阶段是停顿的(Stop the World Event),并且会触发一次普通Mintor GC。对应GC log:GC pause (young) (inital-mark)
2、Root Region Scanning,程序运行过程中会回收survivor区(存活到老年代),这一过程必须在young GC之前完成。
3、Concurrent Marking,在整个堆中进行并发标记(和应用程序并发执行),此过程可能被young GC中断。在并发标记阶段,若发现区域对象中的所有对象都是垃圾,那个这个区域会被立即回收(图中打X)。同时,并发标记过程中,会计算每个区域的对象活性(区域中存活对象的比例)。
在这里插入图片描述
4、Remark, 再标记,会有短暂停顿(STW)。再标记阶段是用来收集 并发标记阶段 产生新的垃圾(并发阶段和应用程序一同运行);G1中采用了比CMS更快的初始快照算法:snapshot-at-the-beginning (SATB)。
5、Copy/Clean up,多线程清除失活对象,会有STW。G1将回收区域的存活对象拷贝到新区域,清除Remember Sets,并发清空回收区域并把它返回到空闲区域链表中。
在这里插入图片描述
6、复制/清除过程后。回收区域的活性对象已经被集中回收到深蓝色和深绿色区域。
在这里插入图片描述
【原文链接】https://blog.csdn.net/qq_31997407/article/details/79735411
https://blog.csdn.net/chixushuchu/article/details/86169548

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值