Java JVM学习整理

JVM概述

  1. 1.8之前
  • 线程共享 : 堆、方法区(运行时常量池)、。
  • 现成私有:虚拟机栈、本地方法栈、程序技术器
  1. 1.8后
  • 现成共享:堆、元空间
  • 现成私有:虚拟机栈、本地方法栈、程序技术器

构成

1. 堆(Heap)

  • 作用:堆是JVM中最大的一块内存区域,主要用于存放对象实例和数组。几乎所有的对象实例都在这里分配内存。堆是垃圾收集器的主要工作区域,通过垃圾回收机制自动回收不再使用的对象,释放堆内存空间。
  • 特点
    • 线程共享:堆被所有线程共享。
    • 内存分代:堆内存被分为年轻代(新生代)和老年代(老年代)。年轻代又分为Eden区、From Survivor(S0)区和To Survivor(S1)区。
    • 可扩展性:堆内存的大小可以动态扩展,但当堆中没有足够内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。

2. 方法区(Method Area)

  • 作用:方法区用于存储已被虚拟机加载的类信息、常量、静态变量等数据。这是所有线程共享的区域。
  • 特点
    • 线程共享:与堆一样,方法区也是所有线程共享的区域。
    • 内存回收:方法区的内存回收主要是针对常量池的回收和对类型的卸载,回收效率相对较低。
    • JDK版本差异:在JDK8及以前,方法区通常被称为永久代(PermGen space),但在JDK8及以后,永久代被移除,方法区被移到了本地内存中,即元空间(Meta Space)。
      3. 程序计数器(Program Counter Register)
  • 作用:程序计数器用于存储下一条要执行的指令的地址,是线程私有的内存区域。它可以看作是当前线程所执行的字节码的行号指示器。
  • 特点
    • 线程私有:每个线程都有自己独立的程序计数器。
      唯一不会抛出OutOfMemoryError的区域:在JVM规范中,程序计数器是唯一没有规定任何OutOfMemoryError情况的区域。

4. Java虚拟机栈(Java Virtual Machine Stacks)

  • 作用:Java虚拟机栈是线程私有的,它的生命周期与线程相同。每个线程在创建时都会创建一个虚拟机栈,每个方法调用(包括同步方法)都会创建一个栈帧,用于存储局部变量、操作数栈、动态链接和方法出口信息。
  • 特点
    • 线程私有:每个线程都有自己的虚拟机栈。
    • 栈帧:每个栈帧对应着一次方法调用,包含了该次方法调用的所有信息。
    • 异常处理:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;如果虚拟机栈动态扩展时无法申请到足够的内存,将抛出OutOfMemoryError异常。

5. 本地方法栈(Native Method Stacks)

  • 作用:本地方法栈与虚拟机栈的作用相似,但它主要用于支持native方法的执行。
  • 特点
    • 线程私有:与虚拟机栈一样,每个线程都有自己的本地方法栈。
    • 异常处理:本地方法栈同样可能抛出StackOverflowError和OutOfMemoryError异常。

创建一个对象的过程

  1. 类加载检查
    当虚拟机遇到一条new指令时,首先会检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,则必须先执行相应的类加载过程。类加载过程包括加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)和初始化(Initialization)五个阶段。

  2. 分配内存
    在类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需的内存大小在类加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从Java堆中划分出来。内存分配方式有两种:“指针碰撞”和“空闲列表”,选择哪种方式取决于Java堆内存是否规整,而Java堆内存是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。

  3. 初始化零值
    内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头)。这一步操作保证了对象的实例字段在Java代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。

  4. 设置对象头
    对象头包含了一些用于管理对象的元信息,如哈希码、锁信息、对象所属的类的元数据的指针等。这些信息对于JVM的运行和垃圾回收等机制都至关重要。

  5. 调用构造方法
    最后,虚拟机将调用对象的构造方法,进行对象的初始化。构造方法是类中用于初始化对象的特殊方法,通过执行构造方法中的代码,对象的状态将被设置为程序员所期望的初始状态。

总结
Java对象的创建过程主要包括类加载检查、分配内存、初始化零值、设置对象头和调用构造方法五个步骤。这个过程是自动完成的,由Java虚拟机(JVM)在运行时进行管理。通过这个过程,Java程序员可以方便地创建和管理对象,而无需担心底层的内存分配和垃圾回收等复杂问题。

此外,需要注意的是,在创建对象时,JVM会采取一系列措施来确保线程安全,如使用CAS(Compare-And-Swap)机制来保证内存分配的原子性,以及使用TLAB(Thread Local Allocation Buffer)来减少线程间的竞争等。这些机制共同协作,使得Java对象的创建过程既高效又安全。

类的加载过程

  1. 加载(Loading)
  • 过程描述:
    • 查找和定义类:Java虚拟机(JVM)通过类的全限定名(包括包名和类名)来查找对应的.class文件,并将其包含的二进制数据读入到JVM中。这个.class文件可以是编译后的.java文件,也可以是通过其他方式生成的。
    • 创建Class对象:JVM在内存中为该类创建一个java.lang.Class对象,这个对象包含了类的所有信息,如方法、变量等。
  • 特点
    • 如果该类已经被加载过,则不会重复加载。
    • 加载过程中,JVM会同时加载该类所依赖的其他类。
  1. 连接(Linking)
    连接阶段又分为三个子阶段:验证(Verification)、准备(Preparation)和解析(Resolution)

    2.1 验证(Verification):

    • 目的:确保加载的类符合JVM的规范,并且不会危害JVM的安全。
    • 内容:包括文件格式验证、元数据验证、字节码验证和符号引用验证等。

    2.2 准备(Preparation):

    • 任务:为类的静态变量分配内存,并设置初始值(注意,这里只是设置了零值,而不是用户定义的初始值)。
    • 内存分配:分配的内存大小在类加载完成后即可确定。

    2.3 解析(Resolution):

    • 目的:将常量池中的符号引用转换为直接引用。
    • 内容:符号引用是类的元数据中的一部分,用于描述类的结构和其他类的关系;而直接引用是内存中的地址,用于直接访问类的字段和方法。
  2. 初始化(Initialization)
    过程描述:

    • 执行类构造器:当类被初始化时,JVM会执行类的构造器方法。这个方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static block)中的语句合并产生的。
    • 执行顺序:方法在多线程环境下被正确地同步,确保只有一个线程能执行该类的初始化。
    • 初始化完成:当方法执行完毕后,类就完成了初始化阶段,此时类就可以被JVM的其他部分使用了。

类加载器的工作机制
Java类加载器采用双亲委派模型(Parent Delegation Model)来加载类。当一个类加载器需要加载一个类时,它会首先将这个任务委托给它的父类加载器,依此类推,直到最顶层的启动类加载器(Bootstrap ClassLoader)。如果父类加载器能够加载这个类,就返回这个类的Class对象;如果父类加载器无法加载这个类,子类加载器才会尝试自己加载。这种机制确保了Java平台的核心类库的安全性和一致性。

总结
Java的类加载过程是一个复杂但精细的过程,它确保了类的正确性和可用性。通过加载、连接和初始化三个阶段,JVM能够安全、有效地加载和使用类。同时,类加载器的工作机制——双亲委派模型——为Java平台的核心类库提供了安全保障。

堆内存结构

  1. 堆内存的结构
    Java堆内存是Java应用程序运行时内存管理的主要区域,用于存储Java对象实例。堆内存具有分代结构,主要包括新生代(Young Generation)和老年代(Old Generation),其中新生代又可以细分为Eden区和两个Survivor区(S0和S1)。在JDK 8及以后的版本中,永久代(PermGen)被元空间(Metaspace)所替代。

  2. 分配策略

  • 新生代(Young Generation)

    • 对象创建:大多数新创建的对象首先被分配到新生代的Eden区。如果Eden区没有足够的空间来存储新对象,就会触发Minor GC(也称为Young GC),以清理不再被引用的对象,并尝试为新对象腾出空间。
    • Survivor区:在Minor GC过程中,如果Eden区中的对象仍然存活,它们会被移动到Survivor区中的一个(S0或S1,具体取决于当前的GC策略和Survivor区的使用情况)。如果对象在多次Minor GC后仍然存活,它们会被移动到老年代。
    • 复制算法:新生代通常使用复制算法来管理内存,即每次GC时,将存活的对象从一个Survivor区复制到另一个Survivor区(或Eden区,如果另一个Survivor区为空),并清理掉原Survivor区的空间。
  • 老年代(Old Generation)

    • 对象晋升:当对象在新生代中经过多次GC后仍然存活,或者对象太大以至于无法在新生代中分配时,它们会被晋升到老年代。
    • Major GC:当老年代空间不足时,会触发Major GC(也称为Full GC),该过程会清理老年代中的不再被引用的对象,并可能涉及对整个堆的清理(包括新生代和老年代)。
  • 永久代/元空间(PermGen/Metaspace)

    • JDK 7及以前:永久代用于存储JVM的元数据,如类的结构、方法和字段信息等。
    • JDK 8及以后:永久代被元空间替代,元空间位于堆外,使用本地内存来存储类的元数据,从而减少了堆内存的压力。
  1. 分配原则
  • 优先新生代:由于新生代的GC效率较高(相对于老年代),因此JVM会优先在新生代分配对象。
  • 大对象直接老年代:对于大对象(通常指大小超过一定阈值的对象),JVM会直接将其分配在老年代,以避免在新生代中频繁进行GC。
  • 本地线程缓冲(TLAB):如果开启了TLAB,JVM会尝试按线程优先在TLAB上分配对象,以减少线程间的竞争。
  1. 堆内存参数配置
  • JVM提供了多个参数来配置堆内存的大小和行为,例如
    -Xms:设置JVM启动时堆的初始大小。
    -Xmx:设置JVM可使用的堆的最大大小。
    -Xmn:设置新生代的大小(JDK 5及以上版本支持)。
    -XX:NewRatio:设置新生代与老年代的比例(例如,2表示新生代占1/3,老年代占2/3)。
    -XX:SurvivorRatio:设置Eden区与Survivor区的比例(例如,8表示Eden区占8/10,两个Survivor区各占1/10)。

GC

  1. 定义与特点
  • yongGC(Young GC/Minor GC)
    • 定义:发生在新生代的垃圾收集动作。新生代是JVM堆内存的一个区域,主要存放新创建的对象。
    • 特点:由于新生代中的对象生命周期较短,因此yongGC相对频繁,且回收速度较快。它通常使用复制算法或标记整理算法进行垃圾回收。
  • fullGC(Full GC/Major GC)
    • 定义:针对整个堆内存(包括年轻代、老年代和永久代/元空间)的回收算法,用于回收那些在yongGC中没有回收的存活对象。
    • 特点:fullGC速度较慢,因为它需要扫描整个堆内存,可能导致应用程序的暂停(Stop-The-World),对系统性能影响较大。
  1. 触发条件
  • yongGC的触发条件
    • 当新生代中的Eden区(或称为伊甸园区)空间不足时,会触发一次yongGC。
    • 如果在yongGC后,Survivor区没有足够的空间存放存活的对象,也可能触发额外的yongGC,或者将对象晋升到老年代。
  • fullGC的触发条件
    • 当老年代空间不足时,会触发fullGC。
    • 在某些情况下,如永久代/元空间不足(对于使用JDK 8之前版本的JVM)、显式调用System.gc()方法(尽管不推荐这么做,因为它只是建议JVM进行fullGC)等,也可能触发fullGC。
  1. 优化建议
  • 调整堆内存大小:通过调整JVM参数(如-Xms、-Xmx、-Xmn)来优化堆内存分配,可以减少fullGC的频率。
  • 优化代码:使用更合理的数据结构,避免内存泄漏,减少大对象的创建和长生命周期对象的数量。
  • 使用对象池:对于需要频繁创建和销毁的对象,可以使用对象池来复用对象,减少垃圾回收的次数。
  • 选择合适的垃圾回收器:JVM提供了多种垃圾回收器(如G1、CMS、Parallel GC等),每种回收器都有其适用的场景和优缺点。根据应用的特点选择合适的垃圾回收器可以优化GC性能。
  1. 总结
    yongGC和fullGC是JVM中垃圾收集过程的两种主要类型,它们各自有不同的特点和触发条件。在设计Java应用时,需要合理配置JVM参数、优化代码和选择合适的垃圾回收器来减少GC的次数和暂停时间,从而提高应用的性能和稳定性。

内存担保机制

  • 定义与目的
    JVM内存分配担保机制的主要目的是在Minor GC之前,检查老年代是否有足够的空间来容纳可能从新生代晋升的对象。如果老年代空间不足,JVM会采取适当的措施,如提前执行Full GC(也称为Major GC或Complete GC),以确保Minor GC的顺利进行,避免内存分配失败。

  • 工作原理

    • 检查老年代空间
      在进行Minor GC之前,JVM会检查老年代的可用空间是否足够。这个判断依据通常包括老年代的最大可用连续空间是否大于新生代所有对象的总空间,或者是否大于历次晋升到老年代对象的平均大小。
    • 决定是否执行Full GC
      如果老年代的可用空间不足以容纳新生代晋升的对象,JVM会根据-XX:HandlePromotionFailure参数的设置来决定是否允许担保失败。
      • 如果HandlePromotionFailure=true(在JDK 7之前的版本中需要显式设置,但JDK 7及其后的版本已默认处理此情况),且老年代剩余空间大于历次晋升到老年代对象的平均大小,JVM会尝试进行一次Minor GC,但这次GC是有风险的,因为可能存在晋升失败的情况。
      • 如果HandlePromotionFailure=false或老年代剩余空间小于历次晋升到老年代对象的平均大小,JVM会提前执行Full GC来清理老年代的空间,以便为新生代晋升的对象腾出足够的空间。
    • 执行GC:
      根据上述判断结果,JVM会执行相应的GC操作。如果执行Minor GC后仍有对象需要晋升到老年代但老年代空间不足,且不允许担保失败,那么JVM会抛出OutOfMemoryError错误。

如何判断对象是否需要回收?

  1. 可达性分析
    可达性分析是Java中判断对象是否存活的主流方法。基本思想是通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连(即GC Roots到这个对象不可达)时,则证明此对象是不可用的。
  • 在Java中,可作为GC Roots的对象包括
    • 虚拟机栈(栈帧中的局部变量表)中引用的对象
    • 方法区中的类静态属性引用的对象
    • 方法区中的常量引用的对象
    • 本地方法栈中JNI(即一般说的Native方法)引用的对象
  1. 引用计数法

引用计数算法是一种简单但效率较低的垃圾收集算法。它使用了一个引用计数器来跟踪每个对象被引用的次数。每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1。任何时刻计数器为0的对象就是不可能再被使用的。然而,这种方法很难解决对象之间循环引用的问题,因此在Java中并没有使用引用计数算法。

垃圾收集算法都有哪些?

  1. 复制算法
  • 特点

    • 将内存分为两个区域,交替使用。
    • 适用于对象存活率低的场景。
  • 工作原理

    • 将堆内存分为两个大小相等的区域(From和To)。
    • 在任意时间点,只有一个区域是活动的,另一个区域是空闲的。
    • 垃圾回收时,扫描活动区域,标记存活对象。
    • 将存活对象复制到空闲区域,并交换两个区域的角色。
  • 优点:

    • 高效:只需遍历一次存活对象,并复制到另一区域。
    • 避免了内存碎片:复制后,存活对象紧凑排列。
  • 缺点:

    • 内存利用率低:理论上只有50%的内存被有效利用。
    • 复制开销大:当存活对象较多时,复制操作成本较高。
  • 应用场景:
    主要应用于新生代(Young Generation)的垃圾回收,因为新生代对象存活率低,适合使用复制算法。

  1. 标记整理算法
  • 特点
    • 标记存活对象后,将存活对象移动到内存的一端。
    • 解决了内存碎片问题。
  • 工作原理
    • 标记阶段:遍历所有对象,标记存活对象。
    • 整理阶段:将存活对象移动到内存的一端,形成连续的空闲内存块。
    • 清除阶段:清理掉边界之外的所有对象。
  • 优点
    • 避免了内存碎片:整理后,内存空间连续可用。
    • 不需要额外的内存空间:相对于复制算法,减少了内存浪费。
  • 缺点
    • 整理开销大:移动对象可能增加性能开销。
    • 可能导致应用线程暂停:标记和整理过程可能导致应用线程暂停。
  • 应用场景
    主要应用于老年代(Old Generation)的垃圾回收,因为老年代对象存活率高,使用标记整理算法可以有效解决内存碎片问题。
  1. 标记清除算法
  • 特点
    • 直接标记并清除垃圾对象。
    • 实现简单,但容易产生内存碎片。
  • 工作原理:
    • 标记阶段:遍历所有对象,标记存活对象。
    • 清除阶段:清除所有未被标记的对象(即垃圾对象)。
  • 优点
    • 实现简单:算法逻辑清晰,易于实现。
    • 不需要移动对象:减少了性能开销。
  • 缺点
    • 内存碎片:清除后会产生大量内存碎片,影响内存分配效率。
    • 可能导致应用线程暂停:标记和清除过程可能导致应用线程暂停。
  • 应用场景
    适用于内存碎片问题不是主要关注点,且对垃圾回收效率要求不高的场景。但在现代JVM中,由于内存碎片问题严重,标记清除算法通常不会单独使用。
  1. 分带收集算法
  • 特点
    • 根据对象的存活周期将内存划分为不同区域(如新生代、老年代)。
    • 初始标记、并发标记、最终标记和筛选回收四个阶段
    • 在不同区域使用不同的垃圾回收算法,新生代采用复制算法,老年代采用标记-整理算法。
    • 面向服务端应用的低暂停垃圾回收器,适用于大堆内存环境
  • 工作原理
    • 新生代:使用复制算法等高效回收策略,因为新生代对象存活率低。
    • 老年代:使用标记整理或标记清除算法,因为老年代对象存活率高且数量相对较少。
  • 优点
    • 提高了垃圾回收的效率:根据对象存活周期采用不同回收策略。
    • 减少了全堆垃圾回收的频率:通过分代管理,降低了老年代垃圾回收对应用性能的影响。
    • 提高了垃圾回收的效率,减少了不必要的内存清理,通过分区和增量回收,优化了停顿时间
  • 缺点
    • 实现复杂:需要维护多个内存区域和不同的回收策略。
    • 在某些情况下可能会引发老年代的垃圾回收:如新生代晋升到老年代的对象过多时。
  • 应用场景
    适用于大多数Java应用程序,特别是那些对象存活周期差异较大的场景。通过分带收集算法,可以更有效地管理内存,提高程序的运行效率。

垃圾回收器

  1. Serial GC(串行垃圾回收器)
  • 特点:单线程执行垃圾回收,适用于单核处理器或小型应用。
  • 垃圾收集算法:年轻代使用标记-复制(Mark-Copy)算法,老年代使用标记-整理(Mark-Sweep-Compact)算法。
  • 原理:在垃圾回收时,会暂停所有应用线程(Stop-The-World, STW),然后使用单线程进行垃圾回收。
  • 优点:简单高效,CPU资源消耗少。
  • 缺点:暂停时间长,不适合多核处理器或大型应用。
  • 应用场景:小型应用、单核处理器环境或资源受限的环境。
  1. ParNew GC(并行新生代垃圾回收器)
  • 特点:多线程执行垃圾回收,主要用于新生代。
  • 垃圾收集算法:标记-复制(Mark-Copy)算法。
  • 原理:与Serial GC类似,但使用多线程并行执行垃圾回收,减少停顿时间。
  • 优点:多线程并行回收,提高回收效率。
  • 缺点:仍需要暂停应用线程,但时间相对较短。
  • 应用场景:需要高吞吐量且对停顿时间有一定要求的应用,常与CMS GC配合使用。
  1. Parallel GC(并行垃圾回收器)
  • 特点:多线程执行垃圾回收,旨在提高吞吐量。
  • 垃圾收集算法:年轻代使用标记-复制(Mark-Copy)算法,老年代使用标记-整理(Mark-Compact)算法。
  • 原理:通过多线程并行执行垃圾回收,减少垃圾回收的总时间,从而提高吞吐量。
  • 优点:高吞吐量,适用于多核处理器。
  • 缺点:在垃圾回收时仍需要暂停应用线程。
  • 应用场景:需要高吞吐量的后端应用和大型多线程应用。
  1. CMS GC(并发标记清除垃圾回收器)
  • 特点:并发执行垃圾回收,旨在减少停顿时间。
  • 垃圾收集算法:基于标记-清除(Mark-Sweep)算法,但增加了并发阶段。
  • 原理:分为初始标记、并发标记、重新标记和并发清除等阶段,其中并发标记和并发清除阶段与应用线程并发执行。
  • 优点:低停顿时间,适用于对响应时间敏感的应用。
  • 缺点:可能导致较高的CPU消耗和内存碎片。
  • 应用场景:Web服务器、交互式应用程序等对响应时间有严格要求的应用。
  1. G1 GC(Garbage-First垃圾回收器)
  • 特点
    • 将堆划分为多个区域(Region),并行、并发地进行垃圾回收。
    • 区域划分:G1将堆内存划分为多个大小相等的独立区域(Region),每个Region的大小可以是1MB到32MB之间,且为2的整数次幂。JVM启动时,根据堆的总大小决定Region的数量,默认不超过2048个,但实际可以超过此值,但通常不推荐。
    • 逻辑分代:虽然物理上不再连续,但逻辑上仍然保留了新生代和老年代的概念。新生代包含Eden区和Survivor区,老年代则包含长期存活的对象。
  • 垃圾收集算法:基于分代收集算法,但引入了更多的并发和并行机制。
  • 垃圾收集策略
    • 优先级列表:G1维护一个优先级列表,根据每个Region的垃圾回收价值(即回收所获得的空间大小与回收所需时间的比值)来决定回收顺序。在每次GC时,优先回收价值最大的Region。
    • 增量回收:G1采用增量回收的方式,即每次只回收部分Region,而不是整个堆。这种方式可以减少停顿时间,提高吞吐量。
    • 混合回收:G1既收集年轻代也收集老年代,这种混合回收方式有助于更均衡地管理堆内存,减少内存碎片。
  • 垃圾收集过程
    G1的垃圾收集过程主要包括以下几个阶段:
    • 初始标记(Initial Marking):标记从GC Roots直接可达的对象,并记录下这些对象的引用关系。
    • 并发标记(Concurrent Marking):与用户线程并发执行,标记出整个堆中所有可达的对象。
    • 最终标记(Final Marking):处理并发标记阶段结束后遗留的少量SATB(Snapshot-At-The-Beginning)记录。
    • 筛选回收(Live Data Counting and Evacuation):根据Region的回收价值,选择并回收价值高的Region。在回收过程中,将存活的对象复制到其他Region,并压缩内存。
  • 巨型对象处理
    巨型对象(Humongous Objects):指大小超过Region大小一半的对象。这些对象直接分配在老年代,并占用连续的Region。G1对巨型对象进行了特殊处理,以优化内存分配和回收。
  • 可预测的停顿时间模型
    G1通过以下方式实现可预测的停顿时间:
    • 参数设置:通过-XX:MaxGCPauseMillis参数设置期望的最大GC停顿时间,JVM会尽力满足此要求,但并非绝对保证。
    • 停顿时间预测:G1利用之前的收集数据,建立一个预测模型,以估计在目标时间内可以收集多少个Region。
  • 并发与并行
    • 并发:G1的某些阶段(如并发标记)可以与用户线程并发执行,减少应用程序的停顿时间。
    • 并行:在垃圾回收阶段,G1使用多个GC线程并行工作,以提高垃圾收集的效率。
  • 原理:通过预测哪些区域最有可能包含垃圾,优先回收这些区域,以减少停顿时间并提高吞吐量。
  • 优点:高吞吐量、低停顿时间,适用于大型堆内存的应用。
  • 缺点:相对于其他回收器,G1 GC的复杂性和配置难度可能稍高
  • 应用场景:需要平衡高吞吐量和低延迟的大型应用。
  1. ZGC(Z Garbage Collector)
  • 特点:几乎所有的垃圾回收工作都是并发进行的,目标是将STW暂停时间限制在数毫秒内。
  • 垃圾收集算法:基于染色指针和读屏障等技术,实现低停顿时间的垃圾回收。
  • 原理:通过并发地标记和重定位对象,以及使用读屏障来确保在垃圾回收过程中应用的正确性。
  • 优点:极低的停顿时间,适用于需要极低延迟的应用。
  • 缺点:相对于其他回收器,ZGC的复杂性和资源消耗可能更高。
  • 应用场景:需要处理大量数据同时要求低停顿时间的应用,如大数据处理和云服务。

三色标记法

三色标记法(Tri-color Marking)是一种用于垃圾回收(Garbage Collection, GC)的算法,它通过将对象分为三种颜色——白色、灰色和黑色,来追踪和确定哪些对象是可达的(reachable),哪些是不可达的(unreachable)即垃圾对象,从而进行回收。以下是关于三色标记法的详细解释:

  1. 三色标记法的基本概念
  • 白色对象:潜在的垃圾对象,表示这些对象尚未被垃圾回收器访问到。在垃圾回收开始时,所有对象都被视为白色。当回收过程结束后,如果某些对象仍然保持为白色,则它们被认为是不可达的,即垃圾对象,可以被回收。
  • 灰色对象:表示这些对象已被垃圾回收器访问到,但回收器还需要对其中的一个或多个指针进行扫描,因为它们可能还指向白色对象。灰色对象类似于一个“波面”,它们需要被进一步处理以确定其引用的对象是否可达。
  • 黑色对象:表示这些对象已被垃圾回收器完全访问并扫描过,其中所有字段都已被检查。黑色对象中的任何一个指针都不可能直接指向白色对象,它们被认为是活跃的对象,不需要被回收。
  1. 三色标记法的执行过程
  • 三色标记法的执行过程大致可以分为以下几个阶段:
    • 准备阶段:在此阶段,垃圾回收器会进行一些准备工作,如启用写屏障(write barrier)等。写屏障是一种在对象引用发生变化时插入的额外操作,用于确保垃圾回收的正确性。
    • 根对象标记:从根对象(如全局变量、执行栈上的对象等)开始,将这些根对象标记为灰色,并放入待处理的灰色集合中。
    • 遍历和标记:从待处理的灰色集合中取出一个灰色对象,将其标记为黑色,并将其所有引用的白色对象标记为灰色,并放入待处理的灰色集合中。重复此过程,直到待处理的灰色集合为空。此时,所有可达的对象都已被标记为黑色,而未被标记为黑色的对象即为不可达的垃圾对象。
    • 清理阶段:在此阶段,垃圾回收器会并发地清理那些被标记为白色的对象,即回收不可达的垃圾对象所占用的内存空间。
  1. 三色标记法的特点
  • 并发性:标记过程与应用程序的执行并发进行,不会阻塞应用程序的运行,减少了垃圾回收对应用程序性能的影响。
  • 高效性:通过三色标记和并发清除的策略,可以高效地进行垃圾回收,减少了停顿时间。
  • 适应性:三色标记法适用于多种垃圾回收器,如JVM中的CMS和G1垃圾回收器,以及Golang的垃圾回收机制。
  1. 三色不变式
  • 在三色标记法中,三色不变式是确保GC正确性的关键规则,它包括强三色不变式和弱三色不变式两种形式:
    • 强三色不变式:不允许黑色对象引用白色对象。即,如果一个对象被标记为黑色,则它不能直接引用任何白色对象。这是通过插入屏障(Insertion Barrier)机制来实现的,当黑色对象引用白色对象时,将白色对象标记为灰色。
    • 弱三色不变式:黑色对象可以引用白色对象,但要求该白色对象必须存在其他灰色对象对它的引用,或者可达它的链路上游存在灰色对象。这是通过删除屏障(Deletion Barrier)机制来实现的,当对象A删除对对象B的引用时,如果对象B是灰色或白色,则将其标记为灰色。
  • 多标的问题
    • 多标就是这个对象原本应该被回收掉的白色对象,被错误的标记成了黑色的存活对象,没有被GC回收掉。这个一般发生在并发标记过程中,该对象还是有引用的,但在过程中多标的话,会产生浮动垃圾,这个问题一般都不需要太去关注,因为这种垃圾一般不多,下一次GC就被回收。
  • 漏标问题
    -一个对象本来应该是黑色存活对象,但是没有被正确的标记上,被错误的当做垃圾回收了。CMS采用的是增量更新G1采用的是原始快照。漏标的出现必须同时满足以下条件:
    1、至少有一个黑色对象在自己被标记之后指向了这个白色对象。
    2、所有的灰色对象在自己引用扫描完成之前删除了对白色对象的引用。
    增量更新打破了1,原始快照打破了2

三色不变式的引入是为了解决在并发GC过程中可能出现的对象丢失或错误回收问题。通过强制性的颜色规则和屏障机制,三色标记法能够确保在标记阶段和清理阶段不会错误地回收仍在使用的对象,同时尽量减少对应用程序性能的影响。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值