JVM虚拟机

本文详细介绍了Java虚拟机(JVM)的概念、组成、类加载过程、运行时数据区域、垃圾回收机制、JIT编译器以及Java内存模型,同时涵盖了性能调优和与其他语言的兼容性。通过这些内容,读者可以全面理解JVM在Java技术中的核心作用和优化策略。
摘要由CSDN通过智能技术生成

JVM虚拟机

在这里插入图片描述

1:什么是JVM?

  1. 概念:

    Java 虚拟机(JVM)是一种能够在不同平台上运行 Java 字节码的虚拟机。它是 Java 技术的核心,负责将 Java 代码翻译成特定平台上的机器码

  2. 作用:

  • 字节码执行:JVM 将 Java 源代码编译成字节码文件,在运行时通过解释器或者即时编译器将字节码转换为特定平台的机器码。

  • 跨平台性:在不同的操作系统上安装不同的JVM ,从⽽实现了跨平台的保证。

    在这里插入图片描述

  • 内存管理: JVM 负责分配和管理程序的内存空间,包括栈内存、堆内存和方法区等,同时也负责进行垃圾回收。

  • 安全性: JVM 提供了安全沙箱机制,可以限制 Java 程序对系统资源的访问,防止恶意代码对系统造成伤害。

  • 性能优化: JVM 通过即时编译器等技术,能够对热点代码进行优化,提高程序的执行效率。

2:JVM 的组成

在这里插入图片描述

  1. 类加载器(ClassLoader): 负责加载类文件,并生成对应的 Class 对象。
  2. 解释器(Interpreter)和即时编译器(Just-In-Time Compiler,JIT): 解释器负责解释字节码并执行相应的指令,即时编译器将频繁执行的热点代码编译成本地机器代码以提高执行效率。
  3. 运行时数据区域: 包括方法区、堆、栈和程序计数器等,用于存储运行时数据。
  4. 垃圾回收器(Garbage Collector): 负责管理内存,自动回收不再使用的对象以避免内存泄漏。
  5. 执行引擎(Execution Engine): 负责执行字节码指令,将其转换为对底层操作系统的调用。

3:类加载过程

JVM 的类加载器(ClassLoader)负责将字节码文件加载到 JVM 中,生成对应的 Class 对象。类加载器通常按照父子关系形成一个层次结构,它们根据不同的策略来查找和加载类文件。以下是 JVM 类加载器的类加载过程:

  1. 加载(Loading): 加载是类加载器的第一步,它会在文件系统或者网络中查找符合命名规则的二进制字节流,然后将其读入内存中,并生成对应的 Class 对象。
  2. 链接(Linking): 链接包括验证、准备和解析三个阶段:
    • 验证(Verification): 验证阶段会检查类文件的格式是否正确,包括语法、语义和字节码内容等方面,防止恶意代码对系统造成危害。
    • 准备(Preparation): 在准备阶段,虚拟机会为类变量分配内存空间,并设置默认初始值,如 int 类型的变量默认值为 0,对象引用类型的变量默认值为 null。
    • 解析(Resolution): 解析阶段会将符号引用转换为直接引用,即将类、接口、字段和方法的符号引用转换为对应的直接内存指针,以便进行调用和访问。
  3. 初始化(Initialization): 初始化是类加载器的最后一步,在这个阶段,虚拟机会对类进行初始化,包括执行静态代码块、初始化静态字段和调用静态方法等操作。

JVM 的类加载器采用了双亲委派模型,即当一个类需要被加载时,先由其父类加载器尝试加载,如果父类加载器无法加载,则由当前类加载器尝试加载。如果当前类加载器也无法加载,则继续向上寻找父类加载器,直到找到启动类加载器为止。这种模型可以保证类的唯一性,避免重复加载,同时提高了安全性。

4:运行时数据区域

JVM 在运行时会将内存划分为不同的数据区域,用于存储不同类型的数据和执行不同的操作。下面是 JVM 运行时的数据区域及其详细解释:

在这里插入图片描述

  1. 程序计数器(Program Counter Register): 程序计数器是一块较小的内存区域,它存储着当前线程所执行的字节码指令的地址。在多线程环境下,每个线程都有自己独立的程序计数器,用于确保线程切换后能够正确地恢复执行。
  2. Java 虚拟机栈(Java Virtual Machine Stacks): 虚拟机栈用于存储方法调用的局部变量、方法参数、返回值和部分中间结果。每个线程在创建时都会分配一个独立的虚拟机栈,每个方法被调用时都会在虚拟机栈上创建一个栈帧,栈帧包含了方法的局部变量表、操作数栈、动态链接、方法返回地址等信息。
  3. 本地方法栈(Native Method Stack): 本地方法栈与虚拟机栈类似,但不是为 Java 方法服务的,而是为 Native 方法(使用其他语言编写的方法)服务的。它也是用于存储局部变量和方法参数的,但是与虚拟机栈不同的是,本地方法栈中的栈帧是与 Native 方法相关的。
  4. 堆(Heap): 堆是 JVM 中最大的一块内存区域,用于存储对象实例。所有通过 new 关键字创建的对象都会被分配到堆上,并且堆是被所有线程共享的。堆被划分为年轻代和老年代,其中年轻代又被进一步划分为 Eden 空间、Survivor 0 空间和 Survivor 1 空间。
  5. 方法区(Method Area): 方法区用于存储已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。方法区也被称为永久代(Permanent Generation),但在 JDK 8 以后,永久代被元空间(Metaspace)所取代。
  6. 运行时常量池(Runtime Constant Pool): 运行时常量池是方法区的一部分,它用于存储编译期生成的各种字面量和符号引用。运行时常量池可以包含字符串字面量、类和接口的全限定名、字段和方法的符号引用等。
  7. 直接内存(Direct Memory): 直接内存并不是 JVM 运行时数据区域的一部分,它是 JDK 提供的一种对 NIO(New Input/Output)的支持,通过使用 Native 方法库直接分配堆外内存,提高了 I/O 的性能。

5:垃圾回收

JVM 的垃圾回收过程是指在运行时,JVM 通过垃圾回收器(Garbage Collector)自动地识别和释放不再使用的内存,以便程序能够继续有效地运行。垃圾回收是 Java 语言中的一项重要特性,它大大减轻了开发人员手动管理内存的负担,提高了程序的健壮性和可靠性。下面是 JVM 垃圾回收的详细过程:

在这里插入图片描述

  1. 标记阶段(Marking Phase): 垃圾回收器首先会对堆中的所有对象进行标记,将所有活动对象标记为“存活”,而未被标记的对象则被认定为“垃圾”。标记过程一般采用的是根搜索算法,从根对象出发,递归地遍历对象引用链,将所有可达的对象标记为“存活”。
  2. 整理阶段(Compacting Phase): 在某些垃圾回收算法中,标记完成后还会对存活对象进行压缩或整理,以减少内存碎片的产生,提高内存的利用率。在这一阶段,存活对象会被移动到堆的某一端,使得堆空间中的存活对象连续排列,从而可以更好地支持后续的内存分配。
  3. 清除阶段(Sweeping Phase): 在标记和整理完成后,垃圾回收器会对未被标记的对象进行清除,即释放占用的内存空间。这一阶段将会回收那些被标记为“垃圾”的对象所占用的内存,并将其加入到可用的内存池中,以便后续的对象分配。
  4. 内存分配(Allocation): 在清除阶段完成后,垃圾回收过程还可能包括内存的重新分配和对象的创建。通常情况下,垃圾回收器会尝试在已清理的内存空间中分配新的对象,以便程序继续执行。

6:JIT编译器(Just-In-Time)

JIT(Just-In-Time)编译器是 Java 虚拟机中的一个重要组成部分,它负责将 Java 字节码即时编译成本地机器代码,以提高程序的执行效率。JIT 编译器通过动态编译字节码,将热点代码(频繁执行的代码段)直接编译成本地代码,从而加速程序的执行。下面是 JVM 虚拟机中 JIT 编译器的使用过程:

在这里插入图片描述

  1. 解释执行阶段(Interpretation Stage): 当 Java 程序启动时,初始阶段会使用解释器对字节码进行解释执行。解释器逐条解释字节码指令,并将其转换为对应的本地机器代码执行。在这个阶段,程序的执行速度较慢,因为每次执行都需要动态地解释字节码。
  2. 即时编译阶段(Just-In-Time Compilation Stage): JIT 编译器在运行时监控程序的执行情况,当发现某些代码块被频繁执行,达到一定的阈值(热点代码),就会触发即时编译。即时编译器会将这些热点代码编译成本地机器代码,以提高执行效率。
  3. 编译优化阶段(Compilation Optimization Stage): JIT 编译器在编译过程中会应用一系列优化技术,如内联展开、循环展开、逃逸分析、常量折叠等,以生成高效的本地机器代码。这些优化技术可以减少不必要的指令、减少内存访问、提高计算效率等,从而进一步提升程序的性能。
  4. 本地代码缓存阶段(Native Code Cache Stage): JIT 编译器生成的本地机器代码会被缓存起来,以便下次再次执行相同的热点代码时可以直接使用已经编译好的本地代码,避免重复编译的开销。

7:java内存模型

Java 内存模型(Java Memory Model,JMM)是 Java 虚拟机用来定义多线程并发访问共享内存的规范。JMM 定义了程序中各种变量(实例字段、静态字段等)的访问方式,以及线程之间如何进行通信和同步。下面是 Java 内存模型的主要内容:

  1. 主内存与工作内存: Java 内存模型将内存分为主内存(Main Memory)和工作内存(Working Memory)。所有线程共享的变量存储在主内存中,每个线程有自己的工作内存,工作内存保存了主内存中的变量副本。
  2. 内存屏障(Memory Barriers): 内存屏障是指一组指令,用于确保特定操作的顺序性和可见性。Java 内存模型通过内存屏障来保证线程之间的通信顺序和数据的可见性。
  3. 原子性操作: Java 内存模型保证对基本数据类型(如 int、long、double 等)的读取和写入是原子性的,即一个线程执行这些操作时,在另一个线程看来,这些操作要么全部完成,要么都没有完成。
  4. happens-before 关系: happens-before 是 Java 内存模型中定义的一种偏序关系,用来描述操作之间的先后顺序。如果操作 A happens-before 操作 B,那么线程在执行操作 B 时,将会看到操作 A 的结果。
  5. volatile 关键字: volatile 关键字用于保证被修饰变量的可见性和禁止指令重排序。当一个变量被声明为 volatile,对该变量的读取和写入操作将直接从主内存中进行,而不会使用线程的工作内存。
  6. synchronized 关键字: synchronized 关键字用于实现对共享资源的互斥访问,保证同一时刻只有一个线程可以访问共享资源。通过 synchronized 关键字可以建立互斥锁,保证临界区代码的原子性。

8:性能调优和故障排查

对 JVM 虚拟机进行性能调优和故障排查是提高应用程序性能和稳定性的重要步骤。下面是一些常用的方法和技巧:

  1. 了解 JVM 监控工具: 了解并使用 JVM 监控工具,如 JDK 自带的 jstat、jps、jstack、jmap、jconsole、VisualVM 等,可以实时监控 JVM 运行状态、线程堆栈、内存使用情况等。
  2. 调整 JVM 内存参数: 根据应用程序的需求和实际情况,合理调整 JVM 内存参数。常用的参数包括-Xmx(最大堆内存大小)、-Xms(初始堆内存大小)、-Xss(线程栈大小)等,可以通过设置这些参数来优化内存使用和垃圾回收性能。
  3. 分析 GC 日志: 开启 GC 日志,并通过分析日志来了解垃圾回收情况。GC 日志可以提供关于内存分配、垃圾回收时间、堆大小等信息,通过分析日志可以判断是否存在内存泄漏、频繁 GC 等问题,并进行相应的优化。
  4. 定位性能瓶颈: 使用性能分析工具,如 Java Mission Control、VisualVM、YourKit 等,可以定位应用程序的性能瓶颈。这些工具可以提供各种性能指标、线程活动、方法调用等信息,帮助开发人员找出应用程序中的瓶颈并进行优化。
  5. 合理使用线程池: 合理使用线程池可以提高应用程序的并发性能。根据应用程序的负载情况和性能需求,选择适当的线程池参数,如线程池大小、任务队列的长度等,避免线程过多或过少而导致的性能问题。
  6. 减少锁竞争: 避免过多的锁竞争可以提高应用程序的并发性能。可以使用粒度更小的锁,或者使用无锁数据结构来减少锁竞争,提高多线程并发执行的效率。
  7. 优化代码和算法: 对应用程序的核心代码进行优化,改善算法和数据结构的设计,可以显著提高应用程序的性能。通过减少不必要的计算、避免频繁的对象创建和销毁等方式,优化代码的执行效率。
  8. 进行压力测试: 使用压力测试工具对应用程序进行压力测试,模拟实际场景下的并发请求。通过观察应用程序在高负载情况下的性能表现,可以发现潜在的问题和性能瓶颈,并进行相应的优化。

9:与其他语言的兼容性

JVM(Java 虚拟机)是专门为 Java 程序设计的虚拟机,但它也可以与其他语言进行兼容。这种兼容性主要通过在 JVM 上运行其他语言的编译器或解释器来实现。以下是 JVM 与其他语言的兼容性的几种方式:

  1. 通过 JVM 语言支持: JVM 提供了支持其他语言的接口和工具,如Java Native Interface(JNI)、Java Native Access(JNA)等,开发人员可以使用这些工具编写本地方法接口(Native Method)来与其他语言进行交互。
  2. 通过 JVM 编程语言: 一些编程语言被设计成可以在 JVM 上运行,比如 Groovy、Scala、Kotlin 等。这些语言都被编译成 Java 字节码,可以直接在 JVM 上执行,与 Java 语言具有较好的兼容性。
  3. 通过特定工具和框架: 有些工具和框架可以将其他编程语言的代码翻译成 Java 字节码,以在 JVM 上运行。例如,Jython 将 Python 代码转换为 Java 字节码运行在 JVM 上,JRuby 将 Ruby 代码转换为 Java 字节码运行在 JVM 上。
  4. 通过虚拟机字节码: 一些语言的编译器可以将其代码编译成 Java 字节码,然后在 JVM 上执行。虽然这种方式需要对语言的特性进行适配,但可以利用 JVM 的优化和跨平台特性。
  5. 通过跨语言框架: 一些跨语言框架如 GraalVM 提供了在 JVM 上运行多种编程语言的能力。GraalVM 支持在同一个虚拟机中同时运行多种语言的代码,实现了不同语言之间的互操作性。

10:常见JVM参数和调优选项:

JVM 参数和调优选项可以根据具体的应用需求和环境来进行调整,下面列举一些常见的 JVM 参数和调优选项:

  1. 堆内存参数:
    • -Xms:设置初始堆内存大小
    • -Xmx:设置最大堆内存大小
    • -Xmn:设置新生代大小
    • -XX:NewRatio:设置新生代和老年代的比例
  2. 垃圾回收参数:
    • -XX:+UseSerialGC:使用串行垃圾回收器
    • -XX:+UseParallelGC:使用并行垃圾回收器
    • -XX:+UseConcMarkSweepGC:使用 CMS 垃圾回收器
    • -XX:+UseG1GC:使用 G1 垃圾回收器
    • -XX:MaxGCPauseMillis:设置最大垃圾回收停顿时间
  3. 线程参数:
    • -XX:ParallelGCThreads:设置并行垃圾回收线程数
    • -XX:ConcGCThreads:设置 CMS 垃圾回收器的并发线程数
    • -XX:ThreadStackSize:设置线程栈大小
  4. 类加载参数:
    • -XX:+TraceClassLoading:跟踪类加载过程
    • -XX:+TraceClassUnloading:跟踪类卸载过程
    • -XX:PermSize:设置永久代大小(Java 8 之前)
  5. 性能监控参数:
    • -XX:+PrintGC:打印垃圾回收日志
    • -XX:+PrintGCDetails:打印详细垃圾回收日志
    • -XX:+PrintGCDateStamps:打印垃圾回收时间戳
    • -XX:HeapDumpOnOutOfMemoryError:内存溢出时生成 Heap Dump 文件
  6. 其他参数:
    • -D:设置系统属性
    • -server:启用服务器模式运行 JVM
      ntGCDateStamps`:打印垃圾回收时间戳
    • -XX:HeapDumpOnOutOfMemoryError:内存溢出时生成 Heap Dump 文件
  7. 其他参数:
    • -D:设置系统属性
    • -server:启用服务器模式运行 JVM
    • -XX:CompileThreshold:设置即时编译阈值
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值