JVM之垃圾回收

四、JVM之垃圾回收

4.1 判断对象已死两种方法

4.1.1 引用计数算法

给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。
客观地说,引用计数算法(Reference Counting)的实现简单,判定效率也很高,在大部分情况下它都是一个不错的算法,也有一些比较著名的应用案例,例如微软公司的COM(Component Object Model)技术、使用ActionScript3的FlashPlayer、Pythoni语言和在游戏脚本领域被广泛应用的Squirrel中都使用了引用计数算法进行内存管理。但是,至少主流的Java虚拟机里面没有选用引用计数算法来管理内存,其中最主要的原因是它很难解决对象之间相互循环引用的问题。

4.1.2 可达性算法

可达性分析算法是从GC Roots开始的,寻找到所有的对象,如果一个对象不可达,则证明这个对象已经死亡,需要被回收,搜索走过的路径称为 引用链(Reference Chain)。
image.png

GC Roots包括以下几种:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象;
  • 方法区中类静态属性引用的对象;
  • 方法区中常量引用的对象;
  • 本地方法栈中JNI(Java Native Interface)引用的对象。

当一个对象不再与任何GC Roots相关联时,说明它已经不再被使用,可以被回收了。当然,这个过程并不是简单的判断一个对象是否被引用,而是经过了复杂的算法来判断对象是否还有被使用的可能。


枚举根节点是GC过程中的一部分,它的目的是为了找到所有的活动对象,以便进行垃圾回收。在Java虚拟机的垃圾回收中,枚举根节点是必须的,而且通常需要在Stop-The-World的情况下进行。
Stop-The-World是一种垃圾回收的机制,即当JVM进行垃圾回收时,会先暂停所有的应用线程,直到垃圾回收结束后再恢复应用线程的执行。这是因为在枚举根节点的过程中,需要保证垃圾回收器遍历到的对象是当前状态下的活动对象,否则可能会导致误判和漏判,从而影响程序的正确性。而暂停所有的应用线程是为了保证枚举根节点过程中不会有新的对象被创建或被引用,从而保证了遍历到的对象是当前状态下的活动对象。
虽然Stop-The-World机制会带来一定的性能损失,但是在垃圾回收时保证正确性是非常重要的。当然,为了减少Stop-The-World带来的影响,现代的垃圾回收器也在不断地优化和改进,比如采用并发标记和清除(Concurrent Mark and Sweep)算法,从而在一定程度上减少了垃圾回收时应用线程的暂停时间。


JVM的引用(4种)

强引用(Strong Reference):强引用是Java中最常用的引用类型,也是默认的引用类型。当一个对象具有强引用时,垃圾回收器不会回收该对象,即使内存不足时也不会回收。只有当对象没有任何强引用指向它时,才会被垃圾回收器回收。

软引用(Soft Reference):软引用是一种比强引用弱一些的引用类型。当内存不足时,JVM可能会回收软引用指向的对象,但是只有当内存不足时才会回收。软引用通常用于缓存中,以便在内存不足时可以释放缓存占用的内存。

弱引用(Weak Reference):弱引用是一种比软引用弱一些的引用类型。当垃圾回收器扫描到弱引用指向的对象时,不管当前内存是否充足,都会回收该对象。弱引用通常用于实现缓存中的对象自动清除。

虚引用(Phantom Reference):虚引用是一种比弱引用更弱的引用类型。虚引用的作用是在对象被垃圾回收器回收时收到一个通知。虚引用必须和引用队列(ReferenceQueue)一起使用,当对象被回收时,JVM会将虚引用加入到引用队列中,以便通知程序对象的回收时间。

4.2 垃圾回收算法

4.2.1 标记-清除

该算法分为两个阶段,第一阶段是标记阶段,标记所有可达对象,第二阶段是清除阶段,清除所有未标记的对象。

不足
  • 一个是效率问题,效率不高
  • 一个是空间问题,会产生大量碎片

image.png

4.2.2 复制算法

该算法将内存分为两个区域,每次只使用其中的一块,当一块内存满了之后,将其中的存活对象复制到另一块内存中,再清除之前使用的内存块。

优势
  • 实现简单
  • 不会产生内存碎片
不足
  • 浪费一半的空间

image.png
HotSpot 虚拟机默认设置两部分空间比例 Eden :Surivor = 8 : 1,也就是每次新生代中可用空间 为整个新生代容量的 90%.

4.2.3 标记-整理

该算法将存活对象移动到内存的一端,然后直接清除边界外的所有内存。实现较为麻烦
image.png

4.2.4 分代收集算法

该算法将堆分为新生代和老年代两个部分,新生代通常使用复制算法,而老年代则使用标记-整理算法或者标记-清除算法。该算法利用新生代对象生命周期短的特点,更频繁地进行垃圾回收,减少老年代的压力。

4.3 几种常见收集器

4.3.1 Serial 收集器

Serial 收集器是一款使用单线程执行的垃圾收集器,采用标记-清除算法。它适用于小型应用或者客户端应用的单机环境,因为其单线程执行的特点,可能会出现应用程序停顿的情况,不适合高并发的场景。

Serial Old

应用场景,单线程、小内存应用
特点,简单高效,内存内存占用低

4.3.2 Parallel 收集器

Parallel 收集器是一款使用多线程执行的垃圾收集器,采用标记-清除算法和复制算法。它适用于需要高吞吐量的场景,如后台运算等。由于采用多线程执行,可以有效地减少垃圾收集的时间,但也可能会占用较多的 CPU 资源。

4.3.3 CMS

CMS(Concurrent Mark Sweep)收集器是一款使用并发执行的垃圾收集器,采用标记-清除算法。它适用于对响应时间有要求的应用,如 Web 应用等。由于采用并发执行的方式,垃圾收集过程对应用程序的影响较小,但其并发执行的特点也可能会造成一些性能上的损失。

CMS 收集齐的垃圾收集分为四步:

  • 初始标记(CMS initial mark):单线程运行,需要 Stop The World,标记 GC Roots 能直达的对象。
  • 并发标记((CMS concurrent mark):无停顿,和用户线程同时运行,从 GC Roots 直达对象开始遍历整个对象图。
  • 重新标记(CMS remark):多线程运行,需要 Stop The World,标记并发标记阶段产生对象。
  • 并发清除(CMS concurrent sweep):无停顿,和用户线程同时运行,清理掉标记阶段标记的死亡的对象。

image.png

4.3.4 G1

G1(Garbage-First)是Java Hotspot VM的一款并行垃圾收集器,它是一种面向服务器端应用和多核处理器的垃圾收集器。与传统的垃圾收集器不同,G1采用了全新的垃圾收集策略,将堆划分为一个个大小相等的区域(Region),并且在垃圾收集时不必全局停顿。它能够非常高效地处理大堆,具有可预测的停顿时间,同时也能够避免碎片的产生。

G1的垃圾回收过程可以分为以下几个阶段:

  1. 初始标记(Initial Mark):标记GC Roots直接关联的对象,并且标记这些对象的标记位为“已经标记”。
  2. 并发标记(Concurrent Mark):从GC Roots直接关联的对象开始,递归遍历对象图,标记所有被引用的对象。
  3. 最终标记(Final Mark):在并发标记阶段之后,需要再次暂停应用程序,完成剩余对象的标记,这个阶段也被称为“再次标记”。
  4. 筛选回收(Live Data Counting and Evacuation):根据不同的策略(如,最小收集集合)筛选出可以回收的Region,将这些Region中的存活对象拷贝到未被使用的Region中,然后清理被使用的Region,最终达到压缩整理的效果。

在G1的垃圾回收过程中,会产生两种类型的Region:Eden Region和Survivor Region。当应用程序申请内存时,对象会被放到Eden Region中。当Eden Region的空间满了之后,会启动一次Minor GC,将Eden Region中的存活对象放到Survivor Region中,并将Eden Region清空。这个过程被称为“复制”。

G1的一个重要特点是,它不像其他垃圾收集器一样将堆分为年轻代和老年代,而是将堆分为一个个大小相等的Region,每个Region既可以被分配给年轻代,也可以被分配给老年代。这使得G1能够非常高效地处理大堆,避免了由于堆内存过大而导致的GC停顿时间过长的问题。

另外,G1还采用了一种基于Region的内存分配方式,而不是传统的基于对象的内存分配方式。这种内存分配方式可以更好地控制内存的碎片化,并且更容易实现垃圾收集器的并行化。同时,G1还支持增量收集和混合收集

关于Region

G1垃圾收集器是一种基于区域的垃圾收集器,其中的Region是G1中最基本的内存管理单元。Region是由连续的Java堆内存构成的,其大小通常为1MB或者更大。

在G1中,Java堆被分成多个大小相等的Region,每个Region可以被分为一下几类:

  1. Eden区:新创建的对象会被分配到Eden区。
  2. Survivor区:在Eden区中,对象被标记为“存活”的,就会被复制到Survivor区。Survivor区一般分为两个,用于轮流存储存活对象。
  3. Old区:Old区用于存放长时间存活的对象。在Old区中,会出现大对象和小对象混杂的情况。

在G1的垃圾回收过程中,会优先回收那些占用空间较小的Region,这样就能够更快地腾出一些空间来分配新的对象。当Region中的对象被回收之后,该Region就会变成“未分配”的状态,即可以重新分配对象的空间。由于Region是以区域的形式进行回收的,因此G1收集器在回收过程中会产生碎片,这也是G1垃圾收集器的一个缺点。

总的来说,G1垃圾收集器是一种高效的、基于区域的垃圾收集器,能够在大型堆内存的场景下提供可预测的暂停时间和高吞吐量,但同时也存在一些缺点,比如收集阶段可能产生碎片。

尽管G1有一些策略来最小化或避免这种情况的发生,但碎片仍然可能会出现,特别是在长时间运行且大量对象分配和回收的情况下。为了缓解这种情况,G1还提供了碎片整理(Fragmentation Collection)机制,通过整理各个Region中的存活对象,把相邻的小内存块合并成更大的连续内存块,从而尽可能避免碎片问题的影响。

4.3.5 对比

收集器名称新生代老年代应用场景特点
Serial串行串行单线程、小内存应用简单高效,内存占用低
ParNew并行串行多核CPU应用简单高效,内存占用低,支持并发标记
Parallel Scavenge并行并行吞吐量优先、高吞吐量应用可控的吞吐量,自适应调节线程数
Serial Old串行串行单线程、小内存应用简单高效,内存占用低
Parallel Old并行并行吞吐量优先、高吞吐量应用可控的吞吐量,内存占用低,使用标记-整理算法
CMS并发并发响应时间优先、需要短暂停顿的应用并发收集,停顿时间短,内存占用低,会产生空间碎片
G1并发并发大内存应用、需要低停顿并发收集,可预测的停顿时间,不会产生空间碎片

五、内存分配和回收策略

5.1 对象优先在Eden分配

大多数情况下,对象在新生代Eden区中分配。当Eden区没有足够空间进行分配时,虚拟机将发起一次Minor GC

Minor GC 与 Full GC
新生代GC(Minor GC): 指发生在新生代的垃圾收集机制,因为Java对象大多都是朝生夕灭的特性,所以Minor GC非常频繁,一般回收速度也比较快
老年代GC(Full GC/ Major GC):指发生在老年代的GC,出现了Major GC,通常会伴随至少一次的 Minor GC。比Minor GC慢10倍以上。

5.2. 大对象直接进入老年代

所谓的大对象是指,需要大量连续内存空间的Java对象,最典型的大对象就是那种很长的字符串以及数组(笔者列出的例子中的byte数组就是典型的大对象)。

5.3. 长期存活的对象进入老年代

既然虚拟机采用了分代收集的思想来管理内存,那么内存回收时就必须能识别哪些对象应放在新生代,哪些对象应放在老年代中。为了做到这点,虚拟机给每个对象定义了一个对象年龄(Age)计数器。如果对象在Eden出生并经过第一次Minor GC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并且对象年龄设为l。对象在Survivor区中
每“熬过”一次Minor GC,年龄就增加1岁,当它的年龄增加到一定程度(默认为l5岁),就将会被晋升到老年代中。对象晋升老年代的年龄阈值,可以通过参数-XX:MaxTenuringThreshold设置。

5.4. 动态对象年龄判定

为了能更好地适应不同程序的内存状况,虚拟机并不是永远地要求对象的年龄必须达到了MaxTenuringThreshold才能晋升老年代,如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到MaxTenuringThreshold中要求的年龄。

5.5. 空间分配担保

在发生Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那么Minor GC可以确保是安全的。如果不成立,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。
如果允许,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次Minor GC,尽管这次Minor GC.是有风险的;如果小于,或者HandlePromotionFailure设置
不允许冒险,那这时也要改为进行一次Full GC。

总结

关于JVM垃圾回收这部分,我们首先得掌握垃圾回收的4种常见算法,然后认识几种常见的垃圾收集器,其中较为重要的是CMS垃圾收集器、G1垃圾收集器。因此,我们要在掌握几种常见的垃圾回收算法的前提下,研究常见垃圾收集器中如何使用各种垃圾回收算法以及对应的垃圾回收过程。
这就是关于JVM垃圾回收这一章的全部内容了,如果你发现有什么不足或错误,欢迎在评论区指出或私信我。
如果觉得该文章对你有帮助,可以关注我,一起学习进步!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值