15-大厂面试题-JVM垃圾回收采用的是什么算法,有什么区别和优劣?

简介: 通过之前的学习,我们知道了JVM会通过**可达性算法**来筛选出哪些对象是可回收的,哪些对象是不可回收的,GCRoots对象是哪些,java的引用类型有哪些以及finlize()方法的作用。同时我们也知道了当一个对象在创建的时候是存放在堆内存中的新生代里的,那么当新生代内存满了后就会触发Minor GC;但是问题是我们如何针对新生代内存进行管理,以及如何进行回收这也是一个值得分析和探讨的问题。

这里针对新生代的垃圾回收算法,叫做复制算法

3.1复制算法

我们先来回顾下之前讲堆内存的结构分配

存储在JVM中的Java对象可以被划分为两类:

  ➷ 一类是生命周期较短的瞬时对象,这类对象的创建和消亡都非常迅速,生命周期短的,及时回收即可。

  ➷ 另外一类对象的生命周期却非常长,在某些极端的情况下还能够与JVM的生命周期保持一致。

  Java堆区进一步细分的话,可以划分为年轻代(YoungGen)和老年代(oldGen),其中年轻代又可以划分为Eden空间、Survivor0空间和Survivor1空间(有时也叫做from区、to区)。


这里大家需要去思考,为什么JVM会分成年轻代和老年代,以及年轻代里面又为什么要再划分出三个区域,这样做的好处是什么?

我们先来分析新生代(年轻代)的复制算法以及所带来的的优劣

1969年Fenichel提出了一种称为“半区复制”(Semispace Copying) 的垃圾收集算法, 它将可用内存按容量划分为大小相等的两块, 每次只使用其中的一块。 当这一块的内存用完了, 就将还存活着的对象复制到另外一块上面, 然后再把已使用过的内存空间一次清理掉。

简单点来说,就是把新生代的内存分为两块,如下图所示:


这时比如我们的代码如下:

public class Test {
   
   
    public static void main(String[] args) {
   
   
        registUser();
    }
    public static void registUser(){
   
   
        User user = new User();
    }
}

那么对应内存中的分配就如下:

那么如果我们假设我们的程序不停止,依然在运行,这时不停的调用registUser()方法生产大量的User对象,对应栈帧已经退出,没有指向对应的对象,那么就会在堆内存中产生大量的垃圾对象:


当新生代第一块区域内容已满,装不下的时候,就会触发Minor GC回收垃圾。

这时,如果我们仅仅是采用标记算法,标记哪些对象是可回收的,哪些对象是不可回收的,然后针对可回收的内容进行回收,那么会导致一个不好的后果,就是产生大量的内存碎片。

内存碎片一般是由于空闲的连续空间比要申请的空间小,导致这些小内存块不能被利用。产生内存碎片的方法很简单,举个例:
假设有一块一共有100个单位的连续空闲内存空间,范围是0~99。如果你从中申请一块内存,如10个单位,那么申请出来的内存块就为0~9区间。这时候你继续申请一块内存,比如说5个单位大,第二块得到的内存块就应该为10~14区间。
如果你把第一块内存块释放,然后再申请一块大于10个单位的内存块,比如说20个单位。因为刚被释放的内存块不能满足新的请求,所以只能从15开始分配出20个单位的内存块。
现在整个内存空间的状态是0~9空闲,10~14被占用,15~24被占用,25~99空闲。其中0~9就是一个内存碎片了。如果10~14一直被占用,而以后申请的空间都大于10个单位,那么0~9就永远用不上了,造成内存浪费。
如果你每次申请内存的大小,都比前一次释放的内村大小要小,那么就申请就总能成功。

如果内存碎片过多,就会造成大量的内存浪费,随着回收的次数越多,这样的碎片可能更多更杂乱,因此这样直接针对一块内容空间回收的做法是不可取的。

因此JVM采用了复制算法,我们图中有一块一直未使用的空间可以派上用场了。当真正发生垃圾回收的时候,JVM会将第一块空间中哪些对象是可回收的,不能回收的进行标记,然后将不可回收的对象统统复制到下面那块区域中,并且复制的时候可以紧凑的排列在一起,最大化利用内存空间:

那么我们可以直接一次性回收掉上面空间的所有垃圾对象,同时有新的对象产生的时候,直接放在下面这块区域进行存储即可。 那么这时上面空间就会腾出,下面空间就月会越来越多:


当下面区域装满的时候,同样按照刚才的逻辑复制存活对象到上面区域,一次性回收下面区域内存。两块区域内存就可以一直重复循环使用。

复制算法的缺点

那么复制算法确实可以解决内存碎片的问题,也使得我们的回收工作更加效率,不过其缺点也是显而易见的。这种复制回收算法的代价是将可用内存缩小为了原来的一半, 空间浪费未免太多了一点 。

如果我们给新生代内存分配一个G的大小,那么两块区域平均分配,各自占512MB内存,从始至终就只有一半的内存可用,这样的算法对内存的使用效率就太低了!

现在的商用Java虚拟机大多都优先采用了这种收集算法去回收新生代, IBM公司曾有一项专门研究对新生代“朝生夕灭”的特点做了更量化的诠释——新生代中的对象有98%熬不过第一轮收集。 因此并不需要按照1∶ 1的比例来划分新生代的内存空间。

在1989年, Andrew Appel针对具备“朝生夕灭”特点的对象, 提出了一种更优化的半区复制分代策略, 现在称为“Appel式回收”。 HotSpot虚拟机的Serial、 ParNew等新生代收集器均采用了这种策略来设计新生代的内存布局。 Appel式回收的具体做法是把新生代分为一块较大的Eden空间和两块较小的Survivor空间, 每次分配内存只使用Eden和其中一块Survivor。 发生垃圾搜集时, 将Eden和Survivor中仍然存活的对象一次性复制到另外一块Survivor空间上, 然后直接清理掉Eden和已用过的那块Survivor空间。 HotSpot虚拟机默认Eden和Survivor的大小比例是8∶ 1, 也即每次新生代中可用内存空间为整个新生代容量的90%(Eden的80%加上一个Survivor的10%) , 只有一个Survivor空间, 即10%的新生代是会被“浪费”的。 当然, 98%的对象可被回收仅仅是“普通场景”下测得的数据, 任何人都没有办法百分百保证每次回收都只有不多于10%的对象存活, 因此Appel式回收还有一个充当罕见情况的“逃生门”的安全设计, 当Survivor空间不足以容纳一次Minor GC之后存活的对象时, 就需要依赖其他内存区域(实际上大多就是老年代) 进行分配担保(Handle Promotion) 。

内存的分配担保好比我们去银行借款, 如果我们信誉很好, 在98%的情况下都能按时偿还, 于是银行可能会默认我们下一次也能按时按量地偿还贷款, 只需要有一个担保人能保证如果我不能还款时, 可以从他的账户扣钱, 那银行就认为没有什么风险了。 内存的分配担保也一样, 如果另外一块Survivor空间没有足够空间存放上一次新生代收集下来的存活对象, 这些对象便将通过分配担保机制直接进入老年代, 这对虚拟机来说就是安全的。

小结

本章节我们介绍了JVM垃圾回收的算法-标记复制算法,以及复制算法的缺点。下一节我们将继续介绍JVM内存的分配以及回收策略,比如:对象优先在Eden分配,大对象直接进入老年代,以及长期存活的对象将进入老年代,动态对象的年龄判断以及空间分配担保原则。

目录
相关文章
|
24天前
|
监控 算法 Java
Java虚拟机(JVM)垃圾回收机制深度剖析与优化策略####
本文作为一篇技术性文章,深入探讨了Java虚拟机(JVM)中垃圾回收的工作原理,详细分析了标记-清除、复制算法、标记-压缩及分代收集等主流垃圾回收算法的特点和适用场景。通过实际案例,展示了不同GC(Garbage Collector)算法在应用中的表现差异,并针对大型应用提出了一系列优化策略,包括选择合适的GC算法、调整堆内存大小、并行与并发GC调优等,旨在帮助开发者更好地理解和优化Java应用的性能。 ####
30 0
|
1天前
|
算法 网络协议 Java
【JVM】——GC垃圾回收机制(图解通俗易懂)
GC垃圾回收,标识出垃圾(计数机制、可达性分析)内存释放机制(标记清除、复制算法、标记整理、分代回收)
|
26天前
|
Java 程序员
Java社招面试题:& 和 && 的区别,HR的套路险些让我翻车!
小米,29岁程序员,分享了一次面试经历,详细解析了Java中&和&&的区别及应用场景,展示了扎实的基础知识和良好的应变能力,最终成功获得Offer。
66 14
|
25天前
|
Java 关系型数据库 数据库
京东面试:聊聊Spring事务?Spring事务的10种失效场景?加入型传播和嵌套型传播有什么区别?
45岁老架构师尼恩分享了Spring事务的核心知识点,包括事务的两种管理方式(编程式和声明式)、@Transactional注解的五大属性(transactionManager、propagation、isolation、timeout、readOnly、rollbackFor)、事务的七种传播行为、事务隔离级别及其与数据库隔离级别的关系,以及Spring事务的10种失效场景。尼恩还强调了面试中如何给出高质量答案,推荐阅读《尼恩Java面试宝典PDF》以提升面试表现。更多技术资料可在公众号【技术自由圈】获取。
|
23天前
|
存储 监控 算法
Java虚拟机(JVM)垃圾回收机制深度解析与优化策略####
本文旨在深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法及参数调优方法。通过剖析垃圾回收的生命周期、内存区域划分以及GC日志分析,为开发者提供一套实用的JVM垃圾回收优化指南,助力提升Java应用的性能与稳定性。 ####
|
27天前
|
机器学习/深度学习 监控 算法
Java虚拟机(JVM)的垃圾回收机制深度剖析####
本文深入探讨Java虚拟机(JVM)的垃圾回收机制,揭示其工作原理、常见算法、性能调优策略及未来趋势。通过实例解析,为开发者提供优化Java应用性能的思路与方法。 ####
39 1
|
27天前
|
Java 编译器 API
深入解析:JDK与JVM的区别及联系
在Java开发和运行环境中,JDK(Java Development Kit)和JVM(Java Virtual Machine)是两个核心概念,它们在Java程序的开发、编译和运行过程中扮演着不同的角色。本文将深入解析JDK与JVM的区别及其内在联系,为Java开发者提供清晰的技术干货。
29 1
|
1月前
|
算法 Java
JVM有哪些垃圾回收算法?
(1)标记清除算法: 标记不需要回收的对象,然后清除没有标记的对象,会造成许多内存碎片。 (2)复制算法: 将内存分为两块,只使用一块,进行垃圾回收时,先将存活的对象复制到另一块区域,然后清空之前的区域。用在新生代 (3)标记整理算法: 与标记清除算法类似,但是在标记之后,将存活对象向一端移动,然后清除边界外的垃圾对象。用在老年代
22 0
|
2月前
|
缓存 算法 Java
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
86 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
|
3月前
|
监控 算法 Java
深入理解Java中的垃圾回收机制在Java编程中,垃圾回收(Garbage Collection, GC)是一个核心概念,它自动管理内存,帮助开发者避免内存泄漏和溢出问题。本文将探讨Java中的垃圾回收机制,包括其基本原理、不同类型的垃圾收集器以及如何调优垃圾回收性能。通过深入浅出的方式,让读者对Java的垃圾回收有一个全面的认识。
本文详细介绍了Java中的垃圾回收机制,从基本原理到不同类型垃圾收集器的工作原理,再到实际调优策略。通过通俗易懂的语言和条理清晰的解释,帮助读者更好地理解和应用Java的垃圾回收技术,从而编写出更高效、稳定的Java应用程序。