终于把JVM垃圾回收的来龙去脉搞清楚了(下)

简介: 终于把JVM垃圾回收的来龙去脉搞清楚了(下)

4 怎么回收垃圾



下面讨论几种常见的垃圾收集算法的核心思想。


4.1 标记-清除算法(Mark-Sweep)


网络异常,图片无法展示
|


标记清除算法(Mark-Sweep)是最基础的一种垃圾回收算法,它分为2部分,从根集合(GC Roots)进行扫描,对存活的对象进行标记,标记完毕后,再扫描整个空间中未被标记的对象,如上图所示,标记-清除算法不需要进行对象的移动,只需对不存活的对象进行处理,在存活对象较多的情况下极为高效,但由于标记-清除算法直接回收不存活的对象,因此会造成内存碎片。


我们知道开辟内存空间时,需要的是连续的内存区域,如果内存碎片都是1M大小的话,这时候我们若需要一个 2M的内存区域,其中有2个 1M 是没法用的。这样就导致,其实我们本身还有这么多的内存的,但却用不了。


4.2 复制算法(Copying)


网络异常,图片无法展示
|


复制算法(Copying)是在标记清除算法上演化而来,解决标记清除算法的内存碎片问题。它开始时把堆分成一个对象面和多个空闲面, 程序从对象面为对象分配空间,当对象面满了,基于copying算法的垃圾收集就从根集合(GC Roots)中扫描活动对象,并将每个活动对象复制到空闲面(使得活动对象所占的内存之间没有空闲洞),这样空闲面变成了对象面,原来的对象面变成了空闲面,程序会在新的对象面中分配内存。


这样就保证了内存空间的连续可用,内存分配时也就不用考虑内存碎片等复杂情况,逻辑清晰,运行高效。然而很明显暴露了另一个问题,空间浪费,代价实在太高。


4.3 标记-整理算法(Mark-Compact)


网络异常,图片无法展示
|


标记整理算法(Mark-Compact)标记过程仍然与标记清除算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,并更新对应的空闲指针,然后再清理掉端指针边界以外的内存区域。


标记整理算法一方面在标记-清除算法上做了升级,解决了内存碎片的问题,也规避了复制算法只能利用一半内存区域的弊端。看起来很美好,但从上图可以看到,它对内存变动更频繁,需要整理所有存活对象的引用地址,在效率上比复制算法要差很多。


4.4 分代收集算法


分代收集算法(Generational Collection)严格来说并不是一种思想或理论,而是融合上述3种基础的算法思想,而产生的针对不同情况所采用不同算法的一套组合拳。


对象存活周期的不同将内存划分为几块,一般是把 Java 堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成收集。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用标记-清理或者标记 - 整理算法来进行回收。


so,另一个问题来了,那内存区域到底被分为哪几块,每一块又有什么特别适合什么算法呢?


4.5 内存模型与回收策略


网络异常,图片无法展示
|


Java 堆(Java Heap)是JVM所管理的内存中最大的一块,堆又是垃圾收集器管理的主要区域,这里我们主要分析一下 Java 堆的结构。


Java堆主要分为2个区域:新生代与老年代,其中新生代内存按照8:1:1的比例又分为 Eden 区和 两个Survivor区( From 和 To 2个区)。


可能这时候大家会有疑问,为什么要分为新生代与老年代呢?而新生代为什么又需要Survivor 区,为什么Survivor区还要再分2个区呢。别急,下面咱就絮叨絮叨。


4.5.1 新生代的回收算法(回收以Copying复制算法为主)


所有新生成的对象首先都是放在新生代的,新生代的目标就是尽可能的快速收集那些生命周期短的对象。


大多数情况下,对象会在新生代 Eden 区中进行分配,当Eden区没有足够空间进行分配时,虚拟机会发起一次Minor GC,Minor GC 相比 Major GC 更频繁,回收速度也更快。


进行Minor GC时,会将Eden区无需回收的对象复制到Survivor的From区(若From区不够,则直接进入Old区),然后清空Eden区。当From区也存放满了时,会将Eden区和From存活的对象放到Survivor的To区,然后清空Eden区和Survivor的From区。此时Survivor的From区是空的,然后将Survivor的From区和To区交换,即保持Survivor的To区为空,如此往复。


当Survivor的To区空间不够,不足以存放Eden 区和 From 存活的对象事,就会将存活对象直接存放到 老年代(Old 区)。

新生代发生的GC也叫做Minor GC,MinorGC发生频率比较高(不一定等Eden区满了才触发)。


(1)为啥需要Survivor区?


不就是新生代到老年代么,直接 Eden 到 Old 不好了吗,为啥要这么复杂。想想如果没有 Survivor 区,Eden 区每进行一次 Minor GC,存活的对象就会被送到老年代,老年代很快就会被填满。而有很多对象虽然一次 Minor GC 没有消灭,但其实也并不会蹦跶多久,或许第二次,第三次就需要被清除。这时候移入老年区,很明显不是一个明智的决定。


所以,Survivor 区相当于是 Eden 区和 Old 区的一个缓冲,类似于我们交通灯中的黄灯。它存在意义就是减少被送到老年代的对象,进而减少 Major GC 的发生。Survivor 的预筛选保证,只有经历16次 Minor GC 还能在新生代中存活的对象,才会被送到老年代。


(2)为啥Survivor需要两个区?


设置两个Survivor区最大的好处就是解决内存碎片化。


我们先假设一下,Survivor如果只有一个区域会怎样。Minor GC执行后,Eden区被清空了,存活的对象放到了Survivor区,而之前Survivor区中的对象,可能也有一些是需要被清除的。


问题来了,这时候我们怎么清除它们?在这种场景下,我们只能标记清除,而我们知道标记清除最大的问题就是内存碎片,在新生代这种经常会消亡的区域,采用标记清除必然会让内存产生严重的碎片化。


因为Survivor有2个区域,所以每次Minor GC,会将之前Eden区和From区中的存活对象复制到To区域。第二次Minor GC时,From与To职责兑换,这时候会将 Eden区和To区中的存活对象再复制到From区域,以此反复。(职责会互换)


这种机制最大的好处就是,整个过程中,永远有一个Survivor space是空的,另一个非空的Survivor space是无碎片的。那么,Survivor为什么不分更多块呢?比方说分成三个、四个、五个?显然,如果Survivor区再细分下去,每一块的空间就会比较小,容易导致Survivor区满,两块Survivor区可能是经过权衡之后的最佳方案。


4.5.2 老年代的回收算法(回收以标记-整理算法为主)


老年代占据着2/3的堆内存空间,只有在 Major GC 的时候才会进行清理,每次 GC 都会触发“Stop-The-World”。内存越大,STW 的时间也越长,所以内存也不仅仅是越大就越好。由于复制算法在对象存活率较高的老年代会进行很多次的复制操作,效率很低,所以老年代这里采用的是标记-整理算法。


除了上述所说,在内存担保机制下,无法安置的对象会直接进到老年代,以下几种情况也会进入老年代。


  • (1)大对象:大对象指需要大量连续内存空间的对象,这部分对象不管是不是“朝生夕死”,都会直接进到老年代。这样做主要是为了避免在 Eden 区及2个 Survivor 区之间发生大量的内存复制。当你的系统有非常多“朝生夕死”的大对象时,得注意了。


  • (2)长期存活对象: 虚拟机给每个对象定义了一个对象年龄(Age)计数器。正常情况下对象会不断的在 Survivor 的 From 区与 To 区之间移动,对象在 Survivor 区中没经历一次 Minor GC,年龄就增加1岁。当年龄增加到15岁时,这时候就会被转移到老年代。当然,这里的15,JVM 也支持进行特殊设置。


  • (3)动态对象年龄: 虚拟机并不重视要求对象年龄必须到15岁,才会放入老年区,如果 Survivor 空间中相同年龄所有对象大小的总合大于 Survivor 空间的一半,年龄大于等于该年龄的对象就可以直接进去老年区,无需等你“成年”。这其实有点类似于负载均衡,轮询是负载均衡的一种,保证每台机器都分得同样的请求。看似很均衡,但每台机的硬件不通,健康状况不同,我们还可以基于每台机接受的请求数,或每台机的响应时间等,来调整我们的负载均衡算法。


5 GC是什么时候触发的



GC分为两种:Major GC(或称为Full GC)和minor GC,老年代采用标记-整理算法的Major GC,新生代采用复制算法的minor GC。新生代是GC收集垃圾的频繁区域。

在最近几个版本的JDK里默认包括了对永生带即方法区的回收(JDK8中无永生带了),出现Full GC的时候经常伴随至少一次的Minor GC,但非绝对的。Major GC的速度一般会比Minor GC慢10倍以上。下边看看有那种情况触发JVM进行Full GC及应对策略。

Minor GC触发条件:


一般情况下,当新对象生成,并且在Eden区申请空间失败时,就会触发触发Minor GC。

Full GC触发条件:


(1)System.gc()方法的调用


此方法的调用是建议JVM进行Full GC,虽然只是建议而非一定,但很多情况下它会触发 Full GC,从而增加Full GC的频率,也即增加了间歇性停顿的次数。强烈影响系建议能不使用此方法就别使用,让虚拟机自己去管理它的内存,可通过通过-XX:+ DisableExplicitGC来禁止RMI(Java远程方法调用)调用System.gc。


(2)老年代空间不足


老年代空间只有在新生代对象转入及创建为大对象、大数组时才会出现不足的现象,当执行Full GC后空间仍然不足,则抛出如下错误: java.lang.OutOfMemoryError: Java heap space为避免以上两种状况引起的Full GC,调优时应尽量做到让对象在Minor GC阶段被回收、让对象在新生代多存活一段时间及不要创建过大的对象及数组。


(3)方法区空间不足


JVM规范中运行时数据区域中的方法区,在HotSpot虚拟机中又被习惯称为永生代或者永生区,Permanet Generation中存放的为一些class的信息、常量、静态变量等数据,当系统中要加载的类、反射的类和调用的方法较多时,Permanet Generation可能会被占满,在未配置为采用CMS GC的情况下也会执行Full GC。如果经过Full GC仍然回收不了,那么JVM会抛出如下错误信息:java.lang.OutOfMemoryError: PermGen space


为避免Perm Gen占满造成Full GC现象,可采用的方法为增大Perm Gen空间或转为使用CMS GC。


(4)通过Minor GC后进入老年代的平均大小大于老年代的可用内存


如果发现统计数据说之前Minor GC的平均晋升大小比目前old gen剩余的空间大,则不会触发Minor GC而是转为触发full GC


(5)由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小。
相关文章
|
1月前
|
监控 算法 Java
Java虚拟机(JVM)的垃圾回收机制深度解析####
本文深入探讨了Java虚拟机(JVM)的垃圾回收机制,旨在揭示其背后的工作原理与优化策略。我们将从垃圾回收的基本概念入手,逐步剖析标记-清除、复制算法、标记-整理等主流垃圾回收算法的原理与实现细节。通过对比不同算法的优缺点及适用场景,为开发者提供优化Java应用性能与内存管理的实践指南。 ####
|
25天前
|
监控 算法 Java
Java虚拟机(JVM)垃圾回收机制深度剖析与优化策略####
本文作为一篇技术性文章,深入探讨了Java虚拟机(JVM)中垃圾回收的工作原理,详细分析了标记-清除、复制算法、标记-压缩及分代收集等主流垃圾回收算法的特点和适用场景。通过实际案例,展示了不同GC(Garbage Collector)算法在应用中的表现差异,并针对大型应用提出了一系列优化策略,包括选择合适的GC算法、调整堆内存大小、并行与并发GC调优等,旨在帮助开发者更好地理解和优化Java应用的性能。 ####
32 0
|
2天前
|
算法 网络协议 Java
【JVM】——GC垃圾回收机制(图解通俗易懂)
GC垃圾回收,标识出垃圾(计数机制、可达性分析)内存释放机制(标记清除、复制算法、标记整理、分代回收)
|
24天前
|
存储 监控 算法
Java虚拟机(JVM)垃圾回收机制深度解析与优化策略####
本文旨在深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法及参数调优方法。通过剖析垃圾回收的生命周期、内存区域划分以及GC日志分析,为开发者提供一套实用的JVM垃圾回收优化指南,助力提升Java应用的性能与稳定性。 ####
|
28天前
|
机器学习/深度学习 监控 算法
Java虚拟机(JVM)的垃圾回收机制深度剖析####
本文深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法、性能调优策略及未来趋势。通过实例解析,为开发者提供优化Java应用性能的思路与方法。 ####
44 1
|
1月前
|
监控 算法 Java
Java虚拟机垃圾回收机制深度剖析与优化策略####
【10月更文挑战第21天】 本文旨在深入探讨Java虚拟机(JVM)中的垃圾回收机制,揭示其工作原理、常见算法及参数调优技巧。通过案例分析,展示如何根据应用特性调整GC策略,以提升Java应用的性能和稳定性,为开发者提供实战中的优化指南。 ####
42 5
|
1月前
|
存储 算法 安全
JVM常见面试题(四):垃圾回收
堆区域划分,对象什么时候可以被垃圾器回收,如何定位垃圾——引用计数法、可达性分析算法,JVM垃圾回收算法——标记清除算法、标记整理算法、复制算法、分代回收算法;JVM垃圾回收器——串行、并行、CMS垃圾回收器、G1垃圾回收器;强引用、软引用、弱引用、虚引用
|
1月前
|
存储 算法 Java
JVM进阶调优系列(10)敢向stop the world喊卡的G1垃圾回收器 | 有必要讲透
本文详细介绍了G1垃圾回收器的背景、核心原理及其回收过程。G1,即Garbage First,旨在通过将堆内存划分为多个Region来实现低延时的垃圾回收,每个Region可以根据其垃圾回收的价值被优先回收。文章还探讨了G1的Young GC、Mixed GC以及Full GC的具体流程,并列出了G1回收器的核心参数配置,帮助读者更好地理解和优化G1的使用。
|
1月前
|
监控 Java 测试技术
Elasticsearch集群JVM调优垃圾回收器的选择
Elasticsearch集群JVM调优垃圾回收器的选择
55 1
|
1月前
|
算法 Java
JVM有哪些垃圾回收算法?
(1)标记清除算法: 标记不需要回收的对象,然后清除没有标记的对象,会造成许多内存碎片。 (2)复制算法: 将内存分为两块,只使用一块,进行垃圾回收时,先将存活的对象复制到另一块区域,然后清空之前的区域。用在新生代 (3)标记整理算法: 与标记清除算法类似,但是在标记之后,将存活对象向一端移动,然后清除边界外的垃圾对象。用在老年代
23 0