JVM!垃圾回收机制!

GC堆,堆的分区,JVM内存分配与回收

1.堆空间的基本结构

新生代:Eden空间  From Survivor  To Survivor空间 老年代 永生代(元空间是直接内存)

        

        大部分情况下,对象都会首先在Eden区分配(当Eden区空间不够,达到阈值的时候就会进行一个Minor GC ),同时大对象直接进入老年代,长期存活的对象直接进入老年代(老年代空间不够的时候会进行Old GC 当老年代空间满了的时候会直接进行Full GC

        

年轻代的垃圾回收机制(Minor GC)

详细步骤:

1.新对象创建:当Java程序创建一个新对象时,会被分配到Eden区

2.Eden区满:当Eden区中的对象数量到达设定的阈值时,就会被认为区满

3.触发MinorGC:当Eden区满的时候,触发机制,这一过程暂停所有的应用线程,并同时根据垃圾回收算法扫描年轻代中存活的对象

4.存活对象复制:找到存活对象后,将他们从Eden区和Survivor区中复制到另一个空的Survivor区中,然后按照年龄大小进行一个排序,其中年龄大于或者等于阈值的对象放入老年代

5.清空Eden区和上一次使用的Survivor区:垃圾回收结束后,Eden区和上一次使用的Survivor区都会被清空

6.From区和To区进行反转:在下一次Minor GC之前,S0区(通常是From区)和S1区(通常是To区)会互换位置。此时,原来的S0区就变成了新的To区,原来的S1区就变成了新的From区

主要步骤:

1.存活对象复制:当进行年轻代的垃圾回收时,Java虚拟机会先找到所有存活的对象,并将它们从Eden区和Survivor区中复制到另一个空的Survivor区中。同时,在这个过程中,Java虚拟机会根据对象的年龄大小进行排序。通常情况下,每经过一次Minor GC,对象的年龄就会增加1;为了避免复制过来的对象占用Survivor区的全部空间,Java虚拟机也会将年龄大于或等于阈值的对象放入老年代中;

2.清空Eden区和上一次使用的Survivor区:在存活对象复制之后,Java虚拟机会清空Eden区和上一次使用的Survivor区。这样,这些区域就可以重新被使用了

3.From区和To区反转:为了保证两个Survivor区都能得到充分利用,每进行一次Minor GC,Java虚拟机会将From区和To区互换位置。所以,在下一次Minor GC之前,原来的S0区(通常是From区)就变成了新的S1区,原来的S1区(通常是To区)就变成了新的S0区;

个人理解:

可以将年轻代的垃圾回收机制比作一家快递公司。

首先,这家快递公司有一个大型的仓库,这个仓库就相当于Java虚拟机的堆内存。在这个仓库里,有三个存放货物的区域:Eden区,S0区和S1区。其中,Eden区可以看作是快递公司的发货区,而S0区和S1区则是快递公司的两个中转站;

1.当一个新的货物被发出时,它会被放入发货区(即Eden区)。如果发货区已经满了,快递公司就会进行“清场”操作,将所有还没有到达目的地的货物重新打包,然后送往中转站(即S0区或S1区)

2.在中转站,快递公司会筛选出所有还未到达目的地的货物,并将它们重新打包,再次送往另一个空的中转站。这个过程就相当于年轻代的存活对象复制过程

3.同时,快递公司也会根据货物的重要程度和时效性来安排不同的运输方式。对于尺寸较小、轻量级、时效性要求高的货物,快递公司会选择空运的方式;而对于尺寸较大、重量较重、时效性要求不高的货物,则会选择陆运,这个过程类比于年轻代的存活对象移动至老年代

4.最后,为了保证中转站能够充分利用,快递公司会定期将两个中转站进行互换。这样,每个中转站都有充分的时间来处理所有的货物。这个过程就相当于From区和To区反转

分配担保机制:

触发机制:用于保证在发生Minor GC时,仍然能够将新生代中所有存活的对象转移到老年代中,从而避免了因内存分配失败而导致的OutOfMemoryError异常

在发生Minor GC时,如果Eden区域和Survivor区域无法容纳所有存活的对象,那么虚拟机会将这些对象(所有存活对象)直接晋升到老年代。为了确保这些对象可以顺利地进入老年代,此时需要使用分配担保机制

基本思想:在进行Minor GC之前,先检查老年代的剩余空间是否足够容纳将要晋升的对象,如果足够,就直接进行Minor GC;否则,就进行一次Full GC来释放老年代已经不再被使用的对象,并把所有存活的对象都移动到老年代中去;

标记-复制算法:将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另⼀块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对内存区间的⼀半进⾏回收;

老年代的垃圾回收机制

年轻代进入老年代的几种情况

1.对象的年龄超过阈值

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

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

详细步骤:

1.初始标记(Initial Mark):在这一阶段,会遍历老年代中的所有对象,并标记所有被直接引用的对象。这个过程需要停止应用程序的执行

2.并发标记(Concurrent Mark):在这一阶段,GC线程会与应用程序线程并发地标记老年代中的所有存活对象。这个过程不需要停止应用程序的执行。

3.重新标记(Remark):在并发标记完成后,需要再次遍历老年代中的所有对象,以便找出在并发标记期间发生变化的对象,并进行标记。这个过程需要停止应用程序的执行。

4.并发清除(Concurrent Sweep):在这一阶段,GC线程会与应用程序线程并发地清理老年代中的无用对象,将它们的内存释放给操作系统。这个过程不需要停止应用程序的执行。

5.重置空间(Compact):在并发清除完成后,可能会出现老年代中存在大量不连续的内存空间的情况。为了减少内存碎片,需要将存活对象压缩到一起,腾出更多的连续空间供应用程序使用。

标记-清除算法: 在标记-清除算法中,垃圾回收器会遍历老年代中的所有对象,并标记出所有活动的对象。然后,所有没有被标记到的对象都会被认为是垃圾对象,需要被清除

弊端:这个过程会造成大量的内存碎片,因为被清除的对象所占用的空间不一定是连续的,留下了许多零散的小块空间。如果新的对象大小超过了其中任何一个小块的大小,那么它就无法被分配到这些小块中,导致内存分配失败

标记-整理算法: 为避免内存碎片问题,可以使用标记-整理算法来进行老年代的垃圾回收。在这个算法中,垃圾回收器将遍历老年代中的所有对象,并标记出所有活动的对象。然后,它将所有活动的对象向一端移动,使得它们所占用的空间是连续的

当所有活动的对象都被移动到一端后,垃圾回收器会清除掉老年代中的所有未被标记的对象,并将剩余的空间全部释放出来。这样,就能够避免内存碎片的问题,提高内存利用率

如何判断对象已经死亡?

1.引用计数器

给对象中添加一个引用计数器,每一个地方引用它+1,引用失效-1,为0就是挂了

2.可达性分析算法

通过⼀系列的称为 “GC Roots” 的对象作为起点,从这些节点开始向下 搜索,节点所⾛过的路径称为引⽤链,当⼀个对象到 GC Roots 没有任何引⽤链相连的话,则证明此对象是不可⽤的

如何判断一个常量是废弃常量?如何判断一个类是无用类?

1.假如在常量池中存在字符串 "abc",如果当前没有任何String对象引⽤该字符串常量的话,就说明 常量 "abc" 就是废弃常量,如果这时发⽣内存回收的话⽽且有必要的话,"abc" 就会被系统清理出常量池

2.该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。 加载该类的 ClassLoader 已经被回收。 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

双亲委派模式是什么?作用?

        双亲委派模式是Java中ClassLoader的一种工作方式,它提供了一种安全机制来避免Java类库的重复加载和保证Java程序的稳定性:

1.当一个Java类需要被加载时,首先会由当前线程的ClassLoader尝试去加载这个类

2.如果自定义的ClassLoader不能够找到这个类,那么它会把该请求委托给父类ClassLoader去处理

3.父ClassLoader也无法加载这个类时,它会继续向上委托给更高层次的ClassLoader,直到Bootstrap ClassLoader

4.如果Bootstrap ClassLoader也无法加载该类,那么会回到原来的子ClassLoader,然后子ClassLoader会抛出ClassNotFoundException异常

在双亲委派模式下,每个ClassLoader都有一个父级ClassLoader。要加载某个类,ClassLoader会首先询问其父级ClassLoader是否已经加载过该类,如果没有,则ClassLoader会自己加载该类。这样,就可以在整个JVM中实现共享类的概念,避免了同一个类被重复加载的情况,同时也保证了程序的稳定性和安全性

常见的垃圾收集器

1.Serial收集器 

优点:

1.低延迟:由于采用单线程进行垃圾收集,可以避免多线程并发带来的上下文切换等开销,从而达到较低的延迟

2.简单高效:由于其简单的实现方式,因此无需过多的系统资源,可以快速地回收垃圾对象

缺点:

1.高并发场景下性能差:由于是单线程执行垃圾收集,对于高并发场景下,可能会出现长时间的停顿,影响系统的吞吐量

2.内存占用较大:由于是在进行垃圾收集时需要独占整个应用进程,因此当垃圾收集时间较长时,可能会导致系统的内存占用较大

场景:

  1. 小型Web应用程序
  2. 移动设备应用程序
  3. 开发阶段的测试环境

算法:

  1. 标记阶段:从根节点开始遍历对象图,将所有被引用的对象打上标记。
  2. 清除阶段:遍历堆内存中的所有对象,将未被标记的对象进行回收

2.Serial Old 收集器

优点:

  1. 实现简单,代码量小,因此运行效率高;
  2. 内存消耗低,适用于内存较小的环境;
  3. 可以和Serial收集器一起使用,成为一个连续的垃圾收集器组合;
  4. 对于较小的应用程序或数据量不大的应用程序,Serial Old可以表现出很好的性能

缺点:

1.单线程导致不能充分利用多核CPU的优势,当需要处理大量数据时,可能会导致STW时间过长;

2.标记-整理算法在处理大对象时,需要额外的开销,因为需要移动大对象,这也会影响垃圾收集器的效率

场景:

  1. 单核CPU环境下的应用程序;
  2. 小型应用程序,因为工作集较小,数据量不大;
  3. 对于启动速度要求较高的应用程序

算法:

        标记-整理算法,该算法可以保证回收后的空间是连续的,减少了碎片的产生。但是,这种算法需要将存活的对象复制到另一段内存中,并且需要对所有存活的对象进行标记,因此在处理大量数据时,会导致STW时间过长

3.ParNew收集器

优点:

  1. 多线程并行处理:使用多个线程同时进行垃圾回收,可以充分利用多核CPU的性能,提高垃圾回收效率。

  2. 低延迟:ParNew收集器采用标记-复制算法,将堆内存分为年轻代和老年代,只对年轻代进行回收。由于年轻代占用堆内存较小,所以垃圾回收速度较快,可以保证低延迟。

  3. 与CMS收集器配合使用:ParNew收集器通常与CMS收集器配合使用,用于对年轻代进行并行回收,CMS收集器则负责对老年代进行并发回收,可以保证整个垃圾回收过程的效率和低延迟

缺点:

  1. 只适用于年轻代垃圾回收:ParNew收集器只能对年轻代进行垃圾回收,并不能对老年代进行回收。

  2. 单线程执行STW操作:在进行年轻代垃圾回收时,需要暂停应用程序的所有线程(STW),这个操作只能由一个线程完成,可能会产生较长的停顿时间

场景:

        低延迟和高吞吐量的场景

算法:

        标记-复制算法

4.CMS 收集器

优点:

  1. 低停顿时间:CMS收集器采用标记-清除算法,在进行垃圾回收时,不需要暂停应用程序的所有线程(STW),可以在应用程序运行的同时进行垃圾回收,从而保证低停顿时间。

  2. 高吞吐量:由于CMS收集器采用并发收集算法,可以在应用程序运行的同时使用多个线程进行垃圾回收,从而保证高吞吐量。

  3. 完全自适应:CMS收集器可以根据应用程序运行情况动态调整垃圾回收策略,以保证最佳的垃圾回收效果和性能

缺点:

  1. 内存碎片问题:由于CMS收集器采用标记-清除算法,会导致堆内存出现大量的内存碎片,可能会影响应用程序的性能。

  2. 并发执行带来的开销:CMS收集器在并发执行垃圾回收时,需要消耗一定的CPU资源和内存空间,可能会对应用程序的性能产生一定的影响

场景:

        适用于需要低停顿时间和高吞吐量的场景,通常与ParNew收集器一起使用

算法:

        标记-清除算法

5.Parallel Scavenge 收集器

优点:

  1. 高吞吐量:Parallel Scavenge收集器可以利用多个CPU核心,并行执行垃圾回收任务,从而保证高吞吐量。

  2. 较短的STW时间:Parallel Scavenge收集器采用标记-复制算法和分代垃圾回收策略,可以在一定程度上减少STW时间。

  3. 自适应调节:Parallel Scavenge收集器可以根据当前系统的负载情况自动调整垃圾回收策略,以达到最佳的垃圾回收效果和性能

缺点:

  1. 内存空间使用问题:Parallel Scavenge收集器采用标记-复制算法,需要分配两个相同大小的内存空间来保存对象,因此在堆空间较小的应用程序中可能会出现内存不足的问题。

  2. 不适合长时间运行的应用程序:由于Parallel Scavenge收集器主要用于新生代的垃圾回收,因此不适合长时间运行的应用程序

场景:

  1. 应用程序具有高吞吐量需求:Parallel Scavenge收集器在进行垃圾回收时,会尽可能地利用多个CPU核心,并且减少STW时间,以提高应用程序的吞吐量。

  2. 应用程序具有较低的响应时间需求:虽然Parallel Scavenge收集器并不是专门为了降低STW时间而设计的,但是由于采用了标记-复制算法和分代垃圾回收策略,可以在一定程度上减少STW时间

算法:

        标记-复制算法

6.Parallel Old 收集器

优点:

  1. 高吞吐量:Parallel Old收集器可以利用多个CPU核心,并行执行垃圾回收任务,从而保证高吞吐量。

  2. 较短的STW时间:Parallel Old收集器采用标记-整理算法,可以在垃圾回收过程中整理内存空间,从而减少STW时间。

  3. 自适应调节:Parallel Old收集器可以根据当前系统的负载情况自动调整垃圾回收策略,以达到最佳的垃圾回收效果和性能

缺点:

  1. 内存空间使用问题:由于Parallel Old收集器采用标记-整理算法,在进行垃圾回收时需要对整个堆空间进行整理,因此需要较大的内存空间。

  2. 不适合长时间运行的应用程序:由于Parallel Old收集器主要用于老年代的垃圾回收,因此不适合长时间运行的应用程序

场景:

  1. 应用程序具有高吞吐量需求:Parallel Old收集器通过并行执行垃圾回收任务,可以尽可能地利用多个CPU核心,并减少STW时间,以提高应用程序的吞吐量。

  2. 应用程序具有较大的堆空间需求:由于Parallel Old收集器采用标记-整理算法,可以在垃圾回收过程中整理内存空间,从而减少内存碎片,适合需要大容量堆空间的应用程序

算法:

        标记-整理算法

7.G1 收集器

优点:

  1. 具有可预测的停顿时间:G1收集器采用分代垃圾回收策略和并发垃圾回收算法,可以在一定程度上减少STW时间,并保证可预测的停顿时间。

  2. 具有高吞吐量:G1收集器可以同时执行多个小型的垃圾回收任务,从而保证高吞吐量。

  3. 内存空间使用效率高:由于G1收集器采用标记-整理算法和分代垃圾回收策略,可以在一定程度上减少内存空间的浪费,并提高内存空间的使用效率

缺点:

  1. 相比其他收集器,G1收集器会消耗更多的CPU资源和内存空间。

  2. 在处理大量短命对象的场景中,G1收集器可能不如CMS等并发收集器的性能表现

场景:

  1. 应用程序具有大内存需求:由于G1收集器可以对整个堆空间进行垃圾回收和整理,因此适合需要大容量堆空间的应用程序。

  2. 应用程序具有低延迟需求:G1收集器采用并发垃圾回收算法和分代垃圾回收策略,可以在一定程度上减少STW时间,并保证较低的延

算法:

        标记-整理算法

猜你喜欢

转载自blog.csdn.net/weixin_64625868/article/details/131026573