当前位置: 首页 > article >正文

JVM的详细讲解

学习目标

  • GC
    • 分代回收
    • 回收器实现
  • 类加载
  • 内存模型
  • 性能调优

  那什么是GC,GC里面的回收分类有哪些?怎么实现,还有什么内存模型里面还有什么东西?怎么调优性能?

GC

JVM中的GC(Garbage Collection,垃圾回收)是Java语言的一个重要特性,它负责自动管理内存,释放那些不再被使用的对象所占用的内存空间。以下是关于JVM中GC的详细介绍:

一、GC的定义与目的

  • 定义:GC即垃圾回收,是指JVM用于释放那些不再被使用的对象所占用的内存。
  • 目的:清除不再使用的对象,防止内存泄漏,提高内存利用率。

二、GC的工作原理

  1. 对象存活判定:
    • 引用计数:虽然这种方法简单有效,但存在循环引用的问题,因此在现代JVM中较少使用。
    • 可达性分析:JVM使用的主流方法。以根集合(如栈上的局部变量、静态变量等)为起点,通过引用链遍历对象图,所有可达的对象都被视为存活对象,其余则为垃圾对象。
  2. 垃圾回收算法
    • 标记-清除算法:首先标记出所有需要回收的对象,然后统一回收被标记的对象所占用的内存空间。标记-清除之后会产生大量的内存碎片。
    • 复制算法:将内存分为两块,每次只使用其中一块。当这一块内存用完,就将还活着的对象复制到另外一块上面。复制算法不会产生内存碎片,但代价是将可用内存缩小为了原来的一半。现在的商业虚拟机都采用这种算法来回收新生代。
  • 标记-整理算法:标记过程与标记-清除算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。这种方法主要用于老年代。
    - 分代收集算法:根据对象的生命周期长短,将堆内存划分为几块,不同块采用不同的回收算法。一般是把Java堆分为新生代和老年代,新生代使用复制算法,老年代使用标记-清除或标记-整理算法。

三、GC收集器
JVM提供了多种GC收集器,每种收集器都有其特定的应用场景和优缺点:

  1. Serial GC:单线程执行GC,适用于单核CPU、新生代空间较小及对暂停时间要求不高的应用。
  2. Parallel GC:多线程执行GC,适用于多核CPU、对吞吐量有高要求的应用。
  3. CMS(Concurrent Mark Sweep)GC:一种以最短回收停顿时间为目标的收集器,适用于对响应时间敏感的应用。但它在并发收集时会产生浮动垃圾,且需要预留一定的空间来避免频繁的全堆GC。
  4. G1(Garbage-First)GC:面向服务端应用的垃圾收集器,它设计用来满足在堆内存不断增大的情况下,减少停顿时间的需求。G1收集器将堆划分为多个大小相等的独立区域(Region),并优先收集垃圾最多的区域。

四、GC的影响与优化

  1. GC对应用程序的影响
    • GC的执行会导致应用程序的暂停(Stop-The-World),但现代JVM通过优化算法和收集器,已经大大减少了这种暂停的时间。
    • 频繁的GC可能会导致应用程序性能下降,因此需要进行GC调优。
  2. GC调优
    • 目标:避免由垃圾回收引起程序性能下降。
    • 核心:通用Jvm参数的设置、特定垃圾回收器的Jvm参数的设置、解决由频繁的FULLGC引起的程序性能问题。
    • 方法:使用监控工具(如jstat、Prometheus+Grafana)、分析GC日志(如使用GC Viewer、GCeasy等工具)、优化JVM参数等。

分代回收

GC(Garbage Collection,垃圾回收)中的分代回收是JVM(Java Virtual Machine)根据对象生命周期长短将堆内存划分为不同区域,并针对不同区域采用不同垃圾回收策略的一种内存管理机制。以下是关于GC中分代回收的详细解释:

一、分代回收的基本概念
在Java应用中,对象的生命周期有长有短。大部分对象生命周期较短,如临时变量,而部分对象生命周期较长,如全局变量、缓存对象等。直接对整个堆进行垃圾回收效率较低,而分代策略通过将堆划分为不同区域(年轻代、老年代和元空间),根据对象的生命周期选择合适的垃圾回收算法,极大地提升了回收效率。

二、分代回收的内存区域

  1. 年轻代(Young Generation):

    • 存储新创建的对象,大部分对象在此分配。
    • 回收频率高,称为“Minor GC”。
    • 进一步细分为Eden区、两个Survivor区(一个用于存储存活对象,另一个为空以备复制)。
  2. 老年代(Old Generation):

    • 存储从年轻代晋升的对象或直接在老年代分配的大对象。
    • 回收频率低,称为“Major GC”或“Full GC”(但需注意,“Full GC”通常还包括方法区的回收,而“Major GC”更专注于老年代的回收)。
  3. 元空间(Metaspace):

    • 从Java 8开始取代永久代(PermGen),用于存储类的元数据。

三、分代回收的垃圾收集类型

  1. 新生代收集(Minor GC/Young GC):
    • 只是新生代的垃圾收集。
    • 当新生代空间不足时触发,如Eden区满。
    • 采用复制算法,将存活对象从一个Survivor区复制到另一个Survivor区(或晋升到老年代),并清理原内存。
  2. 老年代收集(Major GC/Old GC):
    • 只是老年代的垃圾收集。
    • 当老年代空间不足时触发,可能会先尝试触发Minor GC。
    • 采用标记-清除算法或标记-整理算法。
  3. 混合收集(Mixed GC):
    • 收集整个新生代以及部分老年代的垃圾。
    • 目前只有G1 GC具有这种行为。
  4. 整堆收集(Full GC):
    • 收集整个Java堆(包括年轻代和老年代)和方法区的垃圾。
    • 触发机制多样,如老年代空间不足、方法区空间不足等。

四、分代回收的优势

  1. 提高回收效率:通过分代管理,JVM可以针对不同生命周期的对象采用不同的回收策略,从而减少不必要的内存扫描和复制操作。
  2. 减少停顿时间:分代回收策略使得大部分对象的回收都在新生代进行,而新生代的回收速度通常较快,从而减少了应用程序的停顿时间。
  3. 优化内存使用:通过晋升机制,将长期存活的对象从新生代晋升到老年代,避免了新生代空间的浪费。

回收器实现

回收器实现通常指的是在垃圾回收机制中,具体如何实现垃圾收集器的功能和策略。在Java虚拟机(JVM)中,垃圾回收器是内存回收的具体实现,其设计目标是自动检测和回收不再使用的对象所占用的内存空间。以下是关于回收器实现的一些关键要点:

一、垃圾回收器概述
JVM规范中对垃圾收集器应该如何实现并没有任何规定,因此不同的厂商、不同的虚拟机所提供的垃圾收集器都可能会有很大的差别。常见的垃圾回收器包括Serial收集器、ParNew收集器、Parallel Scavenge(PS)收集器、Serial Old收集器、Parallel Old(PO)收集器、CMS(Concurrent Mark-Sweep)收集器和G1(Garbage-First)收集器等。

二、垃圾回收器的实现原理
垃圾回收器通过追踪和分析对象的引用关系,判断哪些对象是可达的,哪些是不可达的,从而进行内存回收。在Java程序执行过程中,对象的生命周期由其是否被引用决定。当对象不再被任何引用指向时,它就成为可回收的垃圾。

三、垃圾回收算法
垃圾回收器通常使用以下几种算法来实现内存回收:

  1. 标记-清除算法:这是最基本的垃圾回收算法。它首先标记所有可达的对象,然后清除未标记的对象。这种算法的优点是实现简单,但缺点是效率不高,且清理出来的空闲内存是不连续的,会产生内存碎片。
  2. 复制算法:该算法将活着的内存空间分为两块,每次只使用其中一块。在垃圾回收时,将正在使用的内存中的存活对象复制到未被使用的内存块中,之后清除正在使用的内存块中的所有对象,交换两个内存的角色,最后完成垃圾回收。这种算法的优点是保证空间的连续性,不会出现碎片问题,但缺点是需要两倍的内存空间。
  3. 标记-整理算法:该算法结合了标记和整理两个阶段。在标记阶段,它标记所有可达的对象;在整理阶段,它将所有的存活对象压缩到内存的一端,按顺序排放,之后清理边界外所有的空间。这种算法的优点是消除了内存碎片,但缺点是移动对象的同时如果对象被其他对象引用,则还需要调整引用的地址。

四、常见的垃圾回收器及其特点

  1. Serial收集器:这是最古老、最稳定的收集器。它使用单个线程进行垃圾收集工作,在进行垃圾回收时会暂停所有用户线程(Stop The World)。Serial收集器简单高效,适合单处理器环境和小型应用。
  2. ParNew收集器:这是Serial收集器的多线程版本,也被称为“并行年轻代收集器”。它可以与CMS收集器配合使用,适用于多处理器环境。
  3. Parallel Scavenge收集器:这是一种多线程并行的垃圾收集器,用于新生代和老年代的回收。它关注的是吞吐量(即CPU用于运行用户代码的时间与CPU总消耗时间的比值),力求高效率利用CPU。
  4. CMS收集器:CMS(Concurrent Mark-Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它采用并发标记和并发清除的方式,减少了垃圾回收的停顿时间,适用于需要低延迟的应用。但CMS收集器对CPU资源敏感,无法处理浮动垃圾,且会产生大量空间碎片。
  5. G1收集器:G1(Garbage-First)收集器是一种面向服务端应用的垃圾收集器。它引入了分而治之的思想,把内存分为一个一个的小块(Region)。G1提供了两种GC模式:Young GC和Mixed GC。它能够更好地管理大内存和多处理器环境,具有更可控的停顿时间和高效的并发能力。

五、选择合适的垃圾回收器
不同的垃圾回收器适用于不同的场景和需求。开发人员可以根据应用的特点选择合适的垃圾回收器来优化性能。例如,对于需要高吞吐量的应用,可以选择Parallel Scavenge收集器;对于需要低延迟的应用,可以选择CMS或G1收集器。

类加载

JVM(Java虚拟机)的类加载机制是Java语言运行时的关键组成部分。下面是对JVM类加载机制的详细讲解:

一、类加载机制概述
类加载机制负责将Java类的字节码文件(*.class文件)加载到内存中,并进行一系列的处理,最终使类能够被虚拟机使用。这个过程包括加载、链接(验证、准备、解析)、初始化等阶段。

二、类加载过程

  1. 加载(Loading)
    • 通过类的完全限定名称(包名+类名)获取定义该类的*.class字节码文件的二进制字节流。
    • 将该字节流表示的静态存储结构转换为Metaspace元空间区的运行时存储结构。
    • 在内存中生成一个代表该类的Class对象,作为元空间区中该类各种数据的访问入口。

加载阶段完成后,*.class字节码文件的类信息数据就会存储在元空间,同时在JVM虚拟机堆区生成一个该类的Class对象。

  1. 链接(Linking)
    • 验证(Verification):确保*.class字节码文件中包含的信息符合当前虚拟机的要求,且不会危害虚拟机的安全。这包括文件格式验证、元数据验证、字节码验证和符号引用验证。
    • 准备(Preparation):为类变量分配内存并设置初始值(一般为0值,常量除外)。实例变量不在此阶段分配内存。
    • 解析(Resolution):将常量池的符号引用替换为直接引用。
  2. 初始化(Initialization)
    • 真正开始执行类中定义的Java程序代码,是虚拟机执行类构造器()方法的过程。
  3. 使用(Using)
    • 程序对类进行实例化、调用其方法等操作。
  4. 卸载(Unloading)
    • 当类不再被使用时,由垃圾回收器进行卸载。这通常发生在程序正常执行结束、程序执行中遇到了异常或错误而异常终止,或者操作系统出现错误或强制结束程序而导致JVM虚拟机进程终止时。

三、类加载器

  1. 类加载器的类型
    • 根类加载器(Bootstrap ClassLoader):负责加载Java的核心类,如java.lang.、java.util.等。这些类位于JDK中的jre/lib/rt.jar或类似的系统路径下。它由JVM提供并且是不可见的,无法直接实例化。
    • 扩展类加载器(Extensions ClassLoader):负责加载JRE的扩展目录(lib/ext)或者由java.ext.dirs系统属性指定的目录中的JAR包的类。
    • 系统类加载器(System ClassLoader,也称为应用类加载器Application ClassLoader):负责在JVM启动时加载来自Java命令的-classpath选项、java.class.path系统属性,或者CLASSPATH环境变量所指定的JAR包和类路径。
    • 自定义类加载器:继承ClassLoader类并重写loadClass方法。程序可以通过ClassLoader的静态方法getSystemClassLoader()来获取系统类加载器,如果没有特别指定,用户自定义的类加载器都以此系统类加载器作为父加载器。
  2. 双亲委派模型
    • 当一个类加载器需要加载一个类时,它首先会检查这个类是否已经被加载过。
    • 如果没有加载过,它会将加载请求委派给它的父类加载器去完成。
    • 如果父类加载器也无法加载这个类,那么子类加载器才会尝试自己去加载这个类。
      这种机制确保了Java核心类库的安全性,因为任何自定义的类加载器都无法加载一个与Java核心类库中类名相同的类。

四、类加载性能优化

  1. 减少类的数量:通过精简代码和移除不必要的依赖,减少类的数量可以降低类加载的复杂度。
  2. 精简类路径:使用工具分析依赖,如jdeps工具,可以帮助检查哪些库是不必要的,从而精简类路径。
  3. 懒加载:延迟加载那些非核心或较少使用的类,可以显著减少初始加载时的内存占用和提升应用的启动效率。
  4. 自定义类加载器:根据需求自定义类加载行为,可以更加灵活地控制类的加载时机、加载顺序以及加载来源。

内存模型

JVM(Java Virtual Machine)的内存模型是Java运行时环境的重要组成部分,它负责管理Java程序在运行时所需的内存。以下是对JVM内存模型的详细讲解:

一、JVM内存模型概述
JVM内存模型主要包括以下几个部分:

  1. 方法区(Method Area):也被称为永久代(在JDK 8之前)或元空间(在JDK 8及之后)。它存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。当方法区无法满足内存分配需求时,会抛出OutOfMemoryError异常。
  2. 堆(Heap):是JVM中内存最大的一块区域,所有线程共享。它用于存储几乎所有的对象实例和数组。堆内存的管理由垃圾收集器负责。堆内存可以进一步细分为新生代(Young Generation)和老年代(Old Generation)。新生代又包括Eden区和两个Survivor区(From和To)。
  3. 栈(Stack):包括虚拟机栈(Java Virtual Machine Stacks)和本地方法栈(Native Method Stack)。每个线程都有一个独立的栈,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。栈内存是线程私有的,生命周期与线程相同。当线程请求分配的栈容量超过Java虚拟机栈允许的最大容量时,会抛出StackOverflowError异常。
  4. 程序计数器(Program Counter Register):是线程私有的,用于保存当前线程正在执行的字节码指令的地址。它是JVM中唯一没有规定OutOfMemoryError情况的区域。

二、各部分详细讲解

  1. 方法区
    • 存储内容:类信息、常量、静态变量、即时编译后的代码等。
    • 内存管理:由JVM自动管理,不需要程序员手动释放。
    • 异常处理:当方法区内存不足时,会抛出OutOfMemoryError异常。
    • 新生代:包括Eden区和两个Survivor区。新创建的对象通常首先在Eden区分配内存。当Eden区内存不足时,会触发Minor GC(年轻代垃圾收集),将存活的对象复制到其中一个Survivor区,并清空Eden区。随着对象存活时间的增加,它们可能会从Survivor区移动到老年代。
    • 老年代:存储生命周期较长的对象。当老年代内存不足时,会触发Full GC(整个堆的垃圾收集),尝试回收内存。如果Full GC无法释放足够的空间,也会抛出OutOfMemoryError异常。
    • 内存分配策略:JVM采用分代收集算法,根据对象的存活时间将内存划分为不同的代,并分别采用不同的垃圾收集策略。
    • 虚拟机栈:每个线程都有一个独立的虚拟机栈,用于存储该线程执行Java方法时的局部变量表、操作数栈、动态链接、方法出口等信息。
    • 本地方法栈:为虚拟机调用Native方法服务。当Java程序调用Native方法时,会进入本地方法栈。
    • 栈内存管理:栈内存由JVM自动管理,当线程结束时,栈内存会自动释放。
  2. 程序计数器
    • 作用:保存当前线程正在执行的字节码指令的地址。它是线程私有的,每个线程都有一个独立的程序计数器。
    • 工作原理:当执行一条指令时,程序计数器会指向下一条需要执行的指令的地址。线程切换后,程序计数器会记录当前线程执行的字节码指令的地址,以便线程再次分配到CPU时继续执行。

三、内存溢出与垃圾回收

  1. 内存溢出(OutOfMemoryError)
    • 原因:应用系统中存在无法回收的内存或使用的内存过多,导致程序运行所需的内存大于JVM能提供的最大内存。
    • 解决方法:增加JVM的堆内存大小、优化代码以减少内存使用、检查并修复内存泄漏等。
  2. 垃圾回收(Garbage Collection)
    • 作用:自动回收不再使用的对象占用的内存空间,以确保JVM能够持续为新的对象分配内存。
    • 垃圾回收算法:包括标记-清除算法、复制算法、标记-整理算法等。不同的垃圾回收器可能会采用不同的算法或算法组合。
    • 垃圾回收器:JVM提供了多种垃圾回收器供选择,如Serial GC、Parallel GC、CMS GC、G1 GC等。不同的垃圾回收器具有不同的特点和适用场景。

性能调优

JVM(Java Virtual Machine)的性能调优是一个复杂且细致的过程,涉及多个方面的调整和优化。以下是对JVM性能调优的详细讲解:

一、JVM性能调优的目标
JVM性能调优的主要目标是提高应用的吞吐量、降低延迟、减少内存占用以及增强应用的稳定性。这通常涉及到对JVM内存管理、垃圾回收器选择、JIT编译器优化等方面的调整。

二、何时进行JVM性能调优
在以下情况下,需要考虑进行JVM性能调优:

  1. Heap内存(老年代)持续上涨达到设置的最大内存值。
  2. Full GC次数频繁,导致应用停顿时间过长。
  3. GC停顿时间过长(通常超过1秒),影响用户体验。
  4. 应用出现OutOfMemory等内存异常。
  5. 系统吞吐量与响应性能不高或下降。

三、JVM性能调优的基本原则
在进行JVM性能调优时,需要遵循以下基本原则:

  1. 大多数的Java应用不需要进行JVM优化,除非遇到性能瓶颈。
  2. 大多数导致GC问题的原因是代码层面的问题,因此应优先进行代码层面的优化。
  3. 上线之前,应先考虑将机器的JVM参数设置到最优。
  4. 减少创建对象的数量,减少使用全局变量和大对象。
  5. JVM优化是不得已的手段,优先进行架构调优和代码调优。

四、JVM性能调优的步骤
JVM性能调优通常包括以下步骤:

  1. 分析GC日志及dump文件:通过GC日志和dump文件分析应用的内存使用情况和垃圾回收行为,确定是否存在性能瓶颈。
  2. 确定JVM调优量化目标:根据应用的需求和实际情况,确定JVM调优的量化目标,如Heap内存使用率、Full GC次数、GC停顿时间等。
  3. 确定JVM调优参数:根据历史JVM参数和调整目标,确定需要调整的JVM参数。
  4. 依次调优内存、延迟、吞吐量等指标:按照内存、延迟、吞吐量的顺序进行优化,每个步骤都是进行下一步的基础。
  5. 对比观察调优前后的差异:通过监控工具观察调优前后的性能差异,确认是否达到调优目标。
  6. 不断的分析和调整:调优是一个迭代的过程,需要不断分析和调整参数,直到找到合适的JVM参数配置。

五、JVM性能调优的关键参数
以下是一些关键的JVM性能调优参数:

  1. 堆内存参数
    • -Xms:设置JVM启动时分配的堆内存大小(初始堆内存)。
    • -Xmx:设置JVM可用的最大堆内存。
    • -Xmn:设置年轻代大小,包括Eden区与两个Survivor区。
  2. 新生代参数
    • -XX:NewRatio:设置年轻代(包括Eden和两个Survivor区)与老年代的比值。
    • -XX:SurvivorRatio:设置年轻代中Eden区与Survivor区的大小比值。
    • -XX:MaxTenuringThreshold:设置垃圾最大年龄,即对象在年轻代存活多少次GC后晋升到老年代。
  3. 元空间参数(JDK 8及以后)
    • -XX:MetaspaceSize:设置初始元空间大小。
    • -XX:MaxMetaspaceSize:设置最大元空间大小。
  4. 垃圾回收器参数
    • -XX:+UseSerialGC:使用Serial垃圾收集器。
    • -XX:+UseParallelGC:使用Parallel垃圾收集器,适用于多核处理器上的高吞吐量应用。
    • -XX:+UseConcMarkSweepGC(或-XX:+UseCMSGC):使用CMS垃圾收集器,适用于对延迟要求较高的应用。
    • -XX:+UseG1GC:使用G1垃圾收集器,适用于大堆内存和多核处理器的场景,可以提供平衡的吞吐量和较低的延迟。
  5. JIT编译器参数
    • -XX:+TieredCompilation:启用分层编译,可以优化启动时间和峰值性能。
      其他JIT编译器参数如编译阈值等,也可以根据实际情况进行调整。
      其他参数:
    • -XX:MaxGCPauseMillis:设置目标GC暂停时间,G1垃圾收集器可以使用此参数。
    • -XX:ParallelGCThreads:设置并行垃圾收集线程数,通常设置为CPU核心数。
    • -XX:ConcGCThreads:设置G1垃圾回收器的并发线程数。

六、JVM性能调优的工具
在进行JVM性能调优时,可以使用以下工具:

  1. VisualVM:Oracle提供的图形化工具,能够实时监控JVM的运行状态,提供内存监控、线程分析、垃圾回收查看等功能。
  2. JProfiler:一款功能全面的Java性能分析工具,支持CPU分析、内存分析、线程分析等。
  3. JConsole:JDK自带的监控和管理工具,可以监控JVM的内存、线程、类加载等信息。
  4. Prometheus + Grafana:开源的监控系统和时序数据库组合,可以实现对JVM性能的可视化监控和分析。

七、JVM性能调优的注意事项

  1. 明确调优目标:在进行JVM调优之前,需要明确调优的目标和需求,以便选择合适的调优方法和工具。
  2. 逐步调整:JVM调优是一个逐步调整的过程,不要一次性修改多个参数或进行大范围的调整。
  3. 结合实际情况:不同的应用场景和硬件环境可能对JVM的性能有不同的影响。因此,在进行JVM调优时,需要结合实际情况进行分析和调整。
  4. 备份原始配置:在进行JVM调优之前,建议备份原始的JVM配置信息。这样,在调优过程中如果出现问题或需要回滚到原始配置时,可以方便地进行操作。

http://www.kler.cn/a/535617.html

相关文章:

  • 基于 Spring Cloud + Spring AI + VUE 的知识助理平台介绍以及问题
  • mac 安装 dotnet 环境
  • 复制粘贴小工具——Ditto
  • DES 3DES 简介 以及 C# 和 js 实现【加密知多少系列_2】
  • 深度分析:网站快速收录与网站内容多样性的关系
  • CSS(三)less一篇搞定
  • 爬虫抓取时遇到反爬策略怎么办?
  • 鸿蒙生态潮起:开发者的逐浪之旅
  • 【工具篇】深度剖析 Veo2 工具:解锁 AI 视频创作新境界
  • Android 中实现 PDF 预览三种方式
  • 【机器学习】K近邻算法的实现
  • (四)QT——QMainWindow——界面菜单设计
  • 【React】setState进阶
  • git 项目的更新
  • C++ auto的使用
  • CVPR2021 | VMI-FGSM VNI-FGSM | 通过方差调整增强对抗攻击的可迁移性
  • vs code 使用教程
  • 采用gitlab的package registry 方式 通过api 上传发布包,解决git命令拉取大文件异常退出问题
  • Deno vs Node.js:性能对比深度解析
  • 2025简约的打赏系统PHP网站源码
  • 语义分割简述
  • Python-memoryutils:内存泄漏检测与防止工具
  • JS:将JS对象格式化为php语法形式(完美支持无unicode编码匹配的正则)
  • Debian安装Seafile
  • 【基于SprintBoot+Mybatis+Mysql】电脑商城项目之修改密码和个人资料
  • android 适配 api 35(android 15) 遇到的问题