读书笔记-->Java虚拟机垃圾收集算法

前言

熟悉虚拟机的垃圾收集算法有助于我们更好的了解Java内存的分配策略。对我来说,以前也有去了解过虚拟机相关的知识点,但是比较零碎。这次拜读《深入理解Java虚拟机》,真是相当后悔自己为啥不早点去读它。

标记-清除算法

该算法可以说是最基础的收集算法(因为后续的收集算法都是基于这种思路并对其缺点加以改进得到的),显而易见,该算法分为两个阶段:“标记”和“清除”。

首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象(采用根搜索算法进行标记)。但该算法有两个主要的缺点:一个是效率问题,标记和清除过程的效率都不高;另外一个是空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致,当程序在以后的运行过程中需要分配较大对象(例如Android中Bitmap对象)时无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。


复制算法

复制算法的出现是为了解决效率问题,它是讲可用内存按容量分为大小相同的两块,每次只使用其中的一块。当其中一块使用完了,就将还活着的对象复制到另外一块内存上面,然后再把已经使用过的内存空间一次清理掉。这样使得每次都是对其中一块进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。但是这种算法的代价就是每次可用的内存压缩过原来的一半,空间代价太大了。


现在的商业虚拟机并不是按照1:1的比例来划分内存空间,而是将内存空间分为一块较大的Eden(伊甸园)空间和两块较小的Survivor空间,每次使用Eden和其中的一块Survivor。当回收时,将Eden和Survivor中还存活着的对象一次性拷贝到另外一块Survivor空间上,最后清理掉Eden和刚才使用过的Survivor的空间。HotSpot(Oracle官方虚拟机)默认Eden和Survivor的大小比例是8:1,也就是每次新生代中可用内存空间为整个新生代容量的90%(80%+10%),只有10%的内存空间被浪费。那么这里就会有个问题,如果保留使用的Survivor内存空间不够时,那么活着的对象将何去何从,这时就需要起来其他内存(这里指老年代)进行分配担保。如果另外一块Survivor空间没有足够的空间存放上一次新生代收集下来的存活对象,这些对象将直接通过分配担保机制进入老年代。关于新生代和老年代相关知识点后续将会介绍。

标记-整理算法

复制收集算法在对象存活率较高时就要执行较多的复制操作,效率将会变低。因此老年代一般不能直接使用这种算法。

根据老年代的特点(用老不死的形容也比较恰当-_-),因此标记-整理算法应运而生。标记的过程和“标记-清理”算法一样,但是后续不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以为的内存。


分代收集算法

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

内存分配与回收策略


对象的内存分配,往大方向上讲,就是在堆上分配,对象主要分配在新生代的Eden区上,如果启动了本地线程分配缓冲,将按照线程优先级在TLAB(Thread Local Allocation Buffer)上分配。少数情况下也可能直接分配在老年代中,分配的规则不是百分之百的固定,与其虚拟机使用的规则相关。

对象优先在Eden分配。当Eden区没有足够的空间进行分配时,虚拟机将发起一次Minor GC(新生代发生的GC)。如果GC期间发现Survivor区无法存放Eden区中存活的对象,那么这些对象将通过分配担保机制提前转移到老年代区。PS:可能大家对老年代、新生代、Eden区等弄糊涂了,这里简要说明下。当JVM堆大小为20M且不可拓展,如果10MB分配了新生代,那么剩下的10M就是分配给了老年代。新生代中包含Eden区和两个Survivor区,按照上面提到的8:1分配关系,那么Eden区内存大小为8M,两个Survivor区分别为1M。

大对象直接进入老年代。所谓大对象就是指,需要大量连续内存空间的Java对象,典型的大对象就是那种长的字符串及数组。大对象对虚拟机的内存分配来说就是一个坏消息,经常出现大对象容易导致内存还有不少空间时就提前出发垃圾收集以获取足够的连续空间来“安置”它们,如果安置的不妥当,异常就出现了。

长期存活的对象将进入老年代。虚拟机既然采用分代收集的思想来管理内存,那内存回收时就必须能识别哪些对象放在新生代,哪些对象放在老年代。为了做到这一点,虚拟机给每个对象定义了一个年龄计数器(有点像年轮)。如果对象在Eden区出生并经过第一次GC后仍然存活,并且能够被Survivor区容纳的话,将被移动到Survivor空间中,并将对象的年龄设为1.对象在Survivor区中每熬过一次Minor GC,年龄就增加1,当它的年龄增加到一定的阈值时(默认为15岁),就将被升级到老年代中。(这里需要注意:对象什么时候进入老年代,年龄达到阈值只是其中一个判断方法,当然还有其他的判断方法)。那么如果老年代的空间不够用时候该怎么办?这时候,虚拟机会发生一次Full GC,顾名思义就是新生代和老年代都要GC。

总结:Java虚拟机垃圾回收机制远比这介绍的复杂,但是,最基本的回收规则,我们还是要了解,这样有助于提高我们在出现内存异常时解决问题的能力。最后文中图片都是截取自《深入理解Java虚拟机》。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值