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

JVM面试知识点手册

第一部分:JVM 概述

1.1 JVM 简介

Java Virtual Machine(JVM) 是 Java 语言的核心组件,负责将 Java 程序编译后的字节码(bytecode)转换为机器指令,并在目标机器上执行。JVM 提供了硬件和操作系统的抽象,使得 Java 程序具有跨平台的特性,即“一次编写,随处运行”(Write Once, Run Anywhere)。

JVM 的核心作用:

  • 字节码执行:JVM 负责执行 Java 编译器生成的字节码文件(.class 文件)。
  • 内存管理:JVM 提供自动的内存管理机制,通过垃圾回收(Garbage Collection, GC)回收无用对象,避免了内存泄漏。
  • 线程管理:JVM 为 Java 提供了多线程支持,管理线程的生命周期。
  • 安全机制:JVM 提供了类加载器和安全管理器,确保执行环境的安全性。

JVM 的跨平台性:
Java 程序的跨平台性是通过 JVM 实现的。每种操作系统和硬件架构都对应不同的 JVM 实现,Java 源代码被编译成字节码后,由对应平台的 JVM 执行,确保程序无需修改就能在不同平台上运行。

1.2 JVM 运行原理简述

JVM 的工作流程大致分为以下几个步骤:

  1. 编译:Java 源代码(.java 文件)通过 Java 编译器(javac)编译为字节码文件(.class 文件)。
  2. 类加载:JVM 的类加载器将字节码文件加载到内存,并进行必要的验证和准备工作。
  3. 字节码执行:JVM 执行字节码文件,将其转换为对应平台的机器码,并通过解释器或即时编译器(JIT)执行。
  4. 内存管理和垃圾回收:JVM 在执行过程中自动管理内存分配,定期通过垃圾回收器回收不再使用的对象。
  5. 线程调度和并发控制:JVM 提供多线程支持,调度和管理 Java 线程的执行。
1.3 JVM 与 JRE、JDK 的关系

Java 开发环境中常常提到三个重要的组成部分:JDK、JRE 和 JVM。

  1. JVM(Java Virtual Machine):

    • JVM 是 Java 程序的运行环境,负责执行字节码文件。它是一种虚拟机,专门为 Java 设计。
  2. JRE(Java Runtime Environment):

    • JRE 是 Java 程序的运行时环境,它包含了 JVM 以及 Java 标准类库(如 Java 核心库、用户界面库等)。简单来说,JRE 是 Java 程序运行所必需的环境,但不包含开发工具。
  3. JDK(Java Development Kit):

    • JDK 是 Java 的开发工具包,包含了开发和调试 Java 程序所需要的工具(如编译器 javac、打包工具 jar 等)以及 JRE。因此,JDK 是开发者所使用的完整工具包,而 JRE 则是专用于运行 Java 程序的环境。

关系图

JDK├── JRE│    ├── JVM│    └── Java 核心类库└── 开发工具(javac、jar 等)

1.4 面试常见问题:
  1. 什么是 JVM,它的作用是什么?

    • JVM 是 Java 虚拟机,负责执行 Java 字节码文件、管理内存、处理线程调度等。
  2. JVM、JRE 和 JDK 之间的区别是什么?

    • JVM 是虚拟机,用于执行字节码;JRE 是包含 JVM 和标准类库的运行环境;JDK 是包含开发工具和 JRE 的完整开发包。
  3. JVM 如何实现跨平台?

    • Java 程序通过编译生成字节码,JVM 将字节码转换为对应平台的机器码,每个平台都有其对应的 JVM 实现,因此实现了跨平台性。
1.5 JVM 结构

JVM 的内部结构复杂且精妙,由多个模块组成,各模块协同工作,保证 Java 程序的高效执行。理解 JVM 的结构可以帮助我们在面试中更好地回答性能调优、类加载等相关问题。

JVM 的核心结构可以划分为以下几个模块:

  1. 类加载器(Class Loader)

    • 负责将字节码文件(.class 文件)加载到 JVM 内存中。
    • 类加载器使用了一种叫做 双亲委派模型 的机制来保证类的加载顺序(将在后续章节详细介绍)。
    • 类加载器的作用是将外部的类文件读取进内存,同时对类文件进行校验、解析、准备和初始化。
  2. 运行时数据区(Runtime Data Areas)

    • JVM 在执行 Java 程序时,会将数据存储在不同的内存区域。运行时数据区可以大致分为以下几个部分:
      • 方法区(Method Area):存储已加载的类信息、常量、静态变量、即时编译后的代码等,属于线程共享的内存区。
      • 堆(Heap):存储对象实例和数组,所有线程共享的内存区,堆是垃圾回收(GC)的重点区域。
      • 虚拟机栈(JVM Stack):每个线程都会创建一个虚拟机栈,用于存储局部变量、操作数栈、动态链接、方法出口等信息。每个方法在执行时都会创建一个栈帧(Stack Frame)。
      • 程序计数器(Program Counter Register):每个线程都有一个独立的程序计数器,记录当前线程执行的字节码指令的地址。
      • 本地方法栈(Native Method Stack):与 JVM Stack 类似,但用于存储本地方法调用时的相关信息。
  3. 执行引擎(Execution Engine)

    • JVM 的执行引擎负责解释并执行字节码文件。它分为两种执行模式:
      • 解释执行:逐行解释字节码并执行。
      • 即时编译执行(Just-In-Time, JIT):将热点代码编译为机器码,直接在 CPU 上执行以提高性能。
    • JVM 还会利用多种优化技术,如内联、逃逸分析等,来提升代码执行效率(将在后续章节详细讲解)。
  4. 本地方法接口(JNI,Java Native Interface)

    • JVM 通过 JNI 提供调用非 Java 代码的能力,例如调用 C/C++ 编写的底层代码或操作系统原生方法。
    • JNI 的作用是帮助 JVM 扩展与底层系统的交互功能,尤其是在需要调用特定硬件或者优化性能时。
  5. 垃圾回收器(Garbage Collector)

    • JVM 提供自动内存管理机制,垃圾回收器负责回收不再被引用的对象,防止内存泄漏。
    • 垃圾回收器通过不同的算法(如标记-清除、标记-整理、分代收集等)和回收器(如 Serial、CMS、G1 等)执行垃圾回收。
1.6 面试常见问题:
  1. JVM 的核心组成部分有哪些?

    • JVM 包含类加载器、运行时数据区、执行引擎、本地方法接口、垃圾回收器等核心模块。
  2. JVM 的运行时数据区是如何划分的?

    • JVM 的内存模型分为方法区、堆、虚拟机栈、程序计数器和本地方法栈,每个区域有不同的用途和生命周期。
  3. 执行引擎中解释器与即时编译器(JIT)的区别是什么?

    • 解释器逐行解释字节码并执行,而 JIT 编译器将热点代码编译为机器码直接执行,以提高性能。
  4. 什么是 JNI(Java Native Interface),它的作用是什么?

    • JNI 是 Java 与其他编程语言(如 C/C++)交互的接口,允许 Java 程序调用本地代码和系统 API。

第二部分:JVM 内存模型

2.1 JVM 内存区域划分

JVM 在运行时将内存划分为多个区域,每个区域负责不同的任务,合理的内存划分帮助 JVM 高效地管理应用程序的资源。JVM 的内存模型大致可以分为以下几个部分:

  1. 方法区(Method Area)

    • 作用:方法区存储已加载的类信息、常量、静态变量、即时编译后的代码等。
    • 特点
      • 属于线程共享的区域,每个线程都可以访问方法区。
      • 方法区中还包含了运行时常量池(Runtime Constant Pool),用于存储编译期生成的各种字面量和符号引用。
      • 方法区在 JVM 规范中是逻辑上的概念,在不同的 JVM 实现中可能会有所差异。在 HotSpot 虚拟机中,方法区被称为“永久代(Permanent Generation)”,但在 Java 8 中,永久代被元空间(Metaspace)取代。
  2. 堆(Heap)

    • 作用:堆是 JVM 中用于存储对象实例的区域,几乎所有的对象实例和数组都存储在堆中。
    • 特点
      • 堆是所有线程共享的内存区域,是垃圾回收的重点区域。
      • Java 堆在逻辑上分为新生代(Young Generation)和老年代(Old Generation)。新生代进一步划分为 Eden 区和两个 Survivor 区(S0 和 S1),用于存储新创建的对象。
      • 堆内存大小可以通过 -Xms-Xmx 参数进行设置,-Xms 指定堆的初始大小,-Xmx 指定堆的最大大小。
  3. 虚拟机栈(JVM Stack)

    • 作用:每个线程在执行 Java 方法时都会创建一个栈帧,用于存储局部变量、操作数栈、动态链接、方法出口等信息,虚拟机栈是这些栈帧的集合。
    • 特点
      • 每个线程都有自己独立的虚拟机栈,线程执行时,栈帧按照方法调用顺序进栈、出栈。
      • 如果线程请求的栈深度大于虚拟机栈的最大深度,会抛出 StackOverflowError
      • 如果虚拟机栈无法申请到足够内存时,会抛出 OutOfMemoryError
  4. 程序计数器(Program Counter Register)

    • 作用:程序计数器是一个较小的内存区域,用于存储当前线程所执行的字节码指令的地址。
    • 特点
      • 每个线程都有独立的程序计数器,用于记录该线程下一条要执行的字节码指令位置。
      • 如果线程执行的是本地方法,程序计数器的值为空(Undefined)。
      • 程序计数器是 JVM 中唯一不会发生内存溢出的区域。
  5. 本地方法栈(Native Method Stack)

    • 作用:本地方法栈用于存储每个线程执行的本地方法的相关信息,类似于虚拟机栈,但它为本地方法(Native 方法)服务。
    • 特点
      • 本地方法栈为 Java 调用 C/C++ 等本地方法时提供了支持。
      • 与虚拟机栈类似,本地方法栈在某些情况下也可能抛出 StackOverflowErrorOutOfMemoryError
2.2 运行时常量池

运行时常量池(Runtime Constant Pool) 是方法区的一部分,用于存储编译期生成的常量,如字符串常量、数值常量等,以及类、方法的符号引用。

特点

  • 动态性:与 Class 文件中的常量池不同,运行时常量池支持动态添加常量。例如,运行时通过 String.intern() 方法将字符串放入常量池。
  • 内存溢出:当常量池无法申请到足够内存时,也会抛出 OutOfMemoryError
2.3 Java 内存模型(JMM)

Java 内存模型(Java Memory Model, JMM)定义了 Java 程序中多线程操作的内存可见性规则,确保不同线程之间对共享变量的读写操作有序可见。

  1. 可见性

    • 当一个线程对共享变量进行了修改,其他线程应该立即看到修改结果。
    • volatile 关键字可以确保变量的可见性,强制将修改后的变量值同步到主内存。
  2. 有序性

    • Java 程序中的操作可能会因为编译器优化或 CPU 的乱序执行而导致执行顺序与代码顺序不同。
    • synchronizedvolatile 关键字可以确保操作的有序性。
  3. 原子性

    • 原子性意味着一个操作是不可分割的,中间不会被打断。
    • Java 的基本数据类型赋值是原子操作,longdouble 类型的赋值在 32 位 JVM 中不是原子的。synchronizedLock 可以确保多线程情况下的原子性操作。
2.4 面试常见问题:
  1. JVM 中堆与栈的区别是什么?

    • 堆是存储对象实例的区域,属于线程共享的内存;栈是线程私有的内存区域,用于存储局部变量、方法调用信息。
  2. 程序计数器的作用是什么?

    • 程序计数器记录当前线程正在执行的字节码指令的地址,确保线程切换后能正确恢复执行。
  3. JVM 内存划分的区域有哪些?

    • JVM 运行时内存划分为方法区、堆、虚拟机栈、程序计数器和本地方法栈,每个区域有不同的职责。
  4. 什么是 Java 内存模型(JMM),它解决了什么问题?

    • JMM 规定了 Java 程序中多线程操作共享变量时的可见性、有序性和原子性,确保线程安全。

第三部分:类加载机制

3.1 类加载过程

类加载机制是 JVM 的核心之一,它负责将类从字节码文件加载到内存并准备执行。类加载过程主要分为以下五个阶段:

  1. 加载(Loading)

    • 通过类的全限定名来获取该类的字节码内容,并将其转换成 JVM 可以识别的类对象(java.lang.Class)。
    • 在加载过程中,JVM 会根据类的全限定名找到对应的字节码文件(通常是 .class 文件),然后通过类加载器(ClassLoader)加载到内存中。
  2. 验证(Verification)

    • 确保字节码文件的正确性和安全性,防止恶意代码损害 JVM 的运行。
    • 验证的过程包括文件格式验证、元数据验证、字节码验证和符号引用验证。例如,它会确保类文件的格式正确、没有非法的操作码等。
  3. 准备(Preparation)

    • 为类的静态变量分配内存,并将其初始化为默认值(如 0null)。
    • 这一步不会给变量赋值实际的值,只会分配内存空间并初始化为默认值,真正的赋值操作会在初始化阶段完成。
  4. 解析(Resolution)

    • 将常量池中的符号引用(Symbolic Reference)替换为直接引用(Direct Reference)。
    • 解析的目标是将常量池中的符号引用转化为内存地址,如类、字段、方法等引用都会在解析阶段被转换为实际的内存地址。
  5. 初始化(Initialization)

    • 执行类的静态代码块(<clinit> 方法)和静态变量的初始化。
    • 在这个阶段,类的静态变量会被赋值为程序员指定的值,执行顺序依照代码中的静态代码块和静态变量声明顺序。
3.2 类加载器

JVM 使用类加载器(ClassLoader)来加载类的字节码文件。每个类在 JVM 中都有且只有一个类加载器负责加载它。Java 提供了多种类加载器,每种类加载器负责加载不同的类。

  1. 启动类加载器(Bootstrap ClassLoader)

    • 作用:启动类加载器是 JVM 内置的,用于加载核心类库(如 java.lang.*java.util.* 等),这些类库存放在 JRE/lib 目录下。
    • 特点:启动类加载器是由本地代码实现的,它加载的是 JVM 启动时所需的核心类,并且不继承自 ClassLoader 类。
  2. 扩展类加载器(Extension ClassLoader)

    • 作用:扩展类加载器加载的是 JRE/lib/ext 目录中的类或通过 java.ext.dirs 系统变量指定的类库。
    • 特点:它是 ClassLoader 的子类,由 Java 编写,主要加载一些扩展类库。
  3. 应用程序类加载器(Application ClassLoader)

    • 作用:也称为系统类加载器,负责加载用户类路径(classpath)下的类,几乎所有应用程序中的类都是由它加载的。
    • 特点:它是 ClassLoader 类的实例,可以通过 ClassLoader.getSystemClassLoader() 方法获取。
  4. 自定义类加载器(Custom ClassLoader)

    • 作用:开发者可以通过继承 ClassLoader 类实现自己的类加载器,以满足特殊需求,比如动态加载类、网络加载类等。
    • 特点:自定义类加载器允许开发者通过覆盖 findClass() 方法来自定义类的加载方式。
3.3 双亲委派机制

双亲委派模型是 JVM 类加载机制中的一个重要原则,它规定当类加载器加载某个类时,首先会将请求委派给父类加载器,父类加载器继续向上委派,直到顶层的启动类加载器。如果父类加载器无法加载该类,才会由当前加载器尝试加载。

双亲委派机制的优点

  1. 避免重复加载:通过双亲委派机制,确保 Java 核心类库不会被重复加载。
  2. 安全性:防止自定义类加载器加载替代 Java 核心类的类,比如 java.lang.String 类,确保系统安全。

打破双亲委派模型

  • 在某些场景下,双亲委派模型需要被打破,例如通过自定义类加载器动态加载某些类。常见的例子是 Java 的 SPI 机制(Service Provider Interface),这需要使用 Thread.getContextClassLoader() 来加载自定义类。
3.4 面试常见问题:
  1. 类加载的五个阶段分别是什么?

    • 加载、验证、准备、解析、初始化。
  2. 双亲委派模型的作用是什么?

    • 双亲委派模型确保类的加载遵循先父后子的原则,避免核心类库被重复加载或篡改。
  3. 自定义类加载器有什么应用场景?

    • 自定义类加载器适用于动态加载类、模块化加载等场景,如 OSGi 模块化系统和热部署等。

第四部分:JVM 垃圾回收

4.1 垃圾回收概述

JVM 提供了自动内存管理机制,通过垃圾回收器(GC)来释放不再使用的对象,避免内存泄漏。垃圾回收的主要目标是回收堆内存中那些不可达的对象。

垃圾回收的必要性

  • 手动管理内存容易导致内存泄漏或内存溢出,而 JVM 自动管理对象生命周期,减少了程序员的负担。
  • 在没有垃圾回收的情况下,程序员需要手动释放内存,增加了开发复杂度和出错几率。

内存泄漏与内存溢出

  • 内存泄漏:指对象不会再被程序使用,但由于存在引用,导致它无法被垃圾回收器回收。
  • 内存溢出(OutOfMemoryError):指 JVM 在运行时无法分配足够的内存,通常是堆或方法区无法申请到足够内存空间。
4.2 垃圾回收算法

垃圾回收器使用不同的算法来识别和回收不再需要的对象,常见的垃圾回收算法有以下几种:

  1. 标记-清除算法(Mark-Sweep)

    • 过程:首先标记出所有需要回收的对象,然后直接清除它们。
    • 优点:实现简单,不需要额外的内存空间。
    • 缺点:标记和清除过程效率低,并且会产生大量内存碎片。
  2. 复制算法(Copying)

    • 过程:将存活的对象从当前内存区域复制到另一个区域,然后清空当前区域。
    • 优点:复制算法可以避免内存碎片问题,分配内存高效。
    • 缺点:需要额外的内存空间进行对象复制。
  3. 标记-整理算法(Mark-Compact)

    • 过程:首先标记出所有存活的对象,然后将存活对象压缩到内存的一端,最后清理掉未使用的内存空间。
    • 优点:避免了内存碎片问题,不需要额外的内存空间。
    • 缺点:移动存活对象的成本较高,适合老年代回收。
  4. 分代收集算法(Generational Garbage Collection)

    • 过程:将堆内存划分为新生代和老年代,不同代的对象使用不同的垃圾回收算法。
    • 优点:适应对象的生命周期特点,新生代回收频繁,老年代回收较少。
    • 缺点:新生代和老年代的垃圾回收算法不同,增加了系统的复杂度。
4.3 垃圾回收器

垃圾回收器是具体执行垃圾回收的组件,常见的垃圾回收器有:

  1. Serial 垃圾回收器

    • 单线程垃圾回收器,适用于单线程环境或内存较小的客户端应用。
  2. Parallel 垃圾回收器

    • 多线程垃圾回收器,适用于多核 CPU,可以在多个 CPU 上并行执行垃圾回收操作。
  3. CMS(Concurrent Mark-Sweep)垃圾回收器

    • 低停顿垃圾回收器,使用标记-清除算法,在应用运行过程中并发执行垃圾回收,适用于需要较短停顿时间的应用。
  4. G1(Garbage-First)垃圾回收器

    • 面向服务端应用的低停顿垃圾回收器,适用于大堆内存,能够同时处理新生代和老年代的垃圾回收,避免 Full GC。

第五部分:JVM 性能调优

5.1 常用 JVM 参数

JVM 提供了一系列参数,用于控制内存大小、垃圾回收行为、性能调优等。合理配置 JVM 参数能够显著提升 Java 应用的运行效率。以下是一些常用的 JVM 参数:

  1. 堆内存大小设置

    • -Xms:设置堆内存的初始大小。例如,-Xms512m 表示 JVM 启动时堆内存大小为 512MB。
    • -Xmx:设置堆内存的最大大小。例如,-Xmx1024m 表示 JVM 堆内存最大可达到 1024MB。
    • 调优建议:将 -Xms-Xmx 设置为相同的值,可以避免 JVM 在运行过程中频繁调整堆内存大小,从而减少性能波动。
  2. 栈内存大小设置

    • -Xss:设置每个线程的栈内存大小。例如,-Xss512k 表示每个线程的栈内存大小为 512KB。
    • 调优建议:对于多线程应用,适当增加栈内存大小可以避免栈溢出(StackOverflowError),但过大的栈内存会消耗更多的物理内存。
  3. 垃圾回收器选择

    • -XX:+UseSerialGC:使用 Serial 垃圾回收器,适用于单线程应用或资源受限的环境。
    • -XX:+UseParallelGC:使用 Parallel 垃圾回收器,适用于高吞吐量、多核 CPU 环境。
    • -XX:+UseConcMarkSweepGC:使用 CMS 垃圾回收器,适用于对低停顿时间有要求的场景。
    • -XX:+UseG1GC:使用 G1 垃圾回收器,适用于大堆内存的低延迟应用。
    • 调优建议:选择合适的垃圾回收器取决于应用的特点,CMS 和 G1 更适合低延迟应用,而 Parallel 更适合高吞吐量的服务端应用。
  4. 永久代/元空间设置

    • -XX:PermSize:设置永久代的初始大小(适用于 Java 7 及以下版本)。
    • -XX:MaxPermSize:设置永久代的最大大小(适用于 Java 7 及以下版本)。
    • -XX:MetaspaceSize:设置元空间的初始大小(适用于 Java 8 及以上版本)。
    • -XX:MaxMetaspaceSize:设置元空间的最大大小(适用于 Java 8 及以上版本)。
    • 调优建议:Java 8 及以上版本采用了元空间来替代永久代,适当设置元空间大小可以避免 OutOfMemoryError
5.2 性能调优工具

JVM 提供了多种性能调优工具,用于监控和分析 Java 应用的运行状态,帮助开发者定位性能瓶颈。常用工具包括:

  1. jstat

    • 作用:用于监控 JVM 运行时的内存和垃圾回收情况。
    • 常用命令
      • jstat -gc pid:显示 GC 相关信息。
      • jstat -gcutil pid:显示各代内存使用情况。
  2. jmap

    • 作用:用于生成 Java 堆的内存快照(heap dump),并可以分析堆中对象的占用情况。
    • 常用命令
      • jmap -heap pid:查看 JVM 堆的详细信息。
      • jmap -dump:format=b,file=heap_dump.hprof pid:生成堆快照文件。
  3. jstack

    • 作用:用于查看线程的堆栈信息,帮助分析线程死锁、线程阻塞等问题。
    • 常用命令
      • jstack pid:输出当前 JVM 进程的线程堆栈信息。
  4. jconsole

    • 作用:JDK 自带的图形化监控工具,用于监控 JVM 的内存、线程、CPU 使用情况。
    • 特点:直观易用,适合实时监控应用的运行状态。
  5. VisualVM

    • 作用:集成了多种分析功能,包括堆快照分析、GC 日志分析、CPU 和内存使用分析等。
    • 特点:支持实时监控和离线分析,适合分析性能问题和内存泄漏。
  6. MAT(Memory Analyzer Tool)

    • 作用:用于分析 Java 堆快照,帮助定位内存泄漏、分析大对象。
    • 特点:可以深入分析大对象及其引用关系,帮助开发者找到内存泄漏的根源。
5.3 常见性能优化策略
  1. 减少 Full GC 触发

    • 问题:Full GC 是垃圾回收中最耗时的一种操作,会暂停所有应用线程,影响应用性能。
    • 优化策略
      • 通过 -Xms-Xmx 设置合理的堆大小,避免频繁的内存分配和回收。
      • 使用 G1 或 CMS 垃圾回收器,这些回收器在执行 Full GC 时更高效。
      • 优化代码中对象的生命周期,避免短命对象大量创建和长时间存活。
  2. 内存泄漏排查

    • 问题:内存泄漏会导致应用的堆内存不断增长,最终触发 OutOfMemoryError
    • 优化策略
      • 使用 jmap 生成堆快照,并用 MAT 分析内存泄漏对象的引用链,找到泄漏源。
      • 避免全局静态变量持有大对象引用,及时清理不再使用的对象。
      • 使用 WeakReferenceSoftReference 代替强引用,减少对象不必要的长时间引用。
  3. 方法区溢出优化

    • 问题:方法区(Java 8 之前称为永久代)可能因类或方法过多而溢出,触发 OutOfMemoryError
    • 优化策略
      • 使用 -XX:MaxMetaspaceSize 设置合理的元空间大小,避免方法区溢出。
      • 对于动态生成类的应用,使用类卸载机制,及时卸载不再使用的类。
  4. 线程调优

    • 问题:线程过多或线程资源争用可能导致 CPU 利用率低或线程阻塞。
    • 优化策略
      • 使用 jstack 分析线程状态,定位死锁或线程饥饿问题。
      • 适当减少线程池中线程数量,避免频繁的上下文切换。
5.4 面试常见问题:
  1. 如何通过 JVM 参数来调优 Java 应用的性能?

    • 可以通过调整堆大小、选择合适的垃圾回收器、设置栈大小等参数来优化 JVM 性能。
  2. 如何定位和解决内存泄漏问题?

    • 使用 jmap 生成堆快照并结合 MAT 工具分析堆中的对象引用链,找到内存泄漏的来源。
  3. Full GC 是什么,它的触发原因有哪些?

    • Full GC 是对整个堆内存(包括新生代和老年代)进行的垃圾回收,通常由堆内存不足、方法区满等原因触发。

第六部分:字节码与执行引擎

6.1 字节码简介

字节码(Bytecode) 是一种面向虚拟机的中间语言,是 JVM 执行 Java 程序的基础。Java 源代码经过编译后生成 .class 文件,其中包含了 JVM 可以理解的字节码指令。

字节码的特点

  • 与硬件无关,跨平台性强。Java 编译器生成的字节码可以在任何 JVM 上运行。
  • 每个字节码指令对应特定的操作,如加载、存储、运算、控制跳转等。

查看字节码

  • 可以使用 JDK 自带的 javap 工具查看 .class 文件中的字节码。例如,javap -c MyClass 可以打印出 MyClass 的字节码指令。
6.2 解释器与即时编译器(JIT)

JVM 执行字节码的方式有两种:解释执行和即时编译(JIT)。

  1. 解释器
    • JVM 通过解释器逐条解释执行字节码。

每次遇到字节码指令时,解释器将其转换为机器码并执行。

  • 优点:启动速度快,因为无需等待字节码的编译。
  • 缺点:解释执行的效率较低,尤其在执行频繁的代码段时,性能会受到影响。
  1. 即时编译器(JIT)
    • JIT 编译器在运行时将热点代码(执行频率高的代码)编译为机器码,直接在 CPU 上执行。
    • 优点:通过将热点代码编译为机器码,JIT 提升了程序的执行效率。
    • 缺点:JIT 编译需要额外的时间和资源,可能在程序启动阶段增加延迟。

JVM 的 JIT 编译器通常分为两个阶段:

  • C1 编译器:进行简单优化,生成较快的机器码,适合应用启动阶段使用。
  • C2 编译器:进行复杂优化,生成更高效的机器码,适合长时间运行的热点代码。
6.3 逃逸分析与锁消除
  1. 逃逸分析

    • 逃逸分析是 JIT 编译器的优化技术,用于判断对象的作用范围。如果对象只在方法内部使用而不会逃逸到方法外部,则可以将其分配在栈上而不是堆上,从而减少堆内存分配和垃圾回收的压力。
  2. 锁消除

    • 锁消除是基于逃逸分析的一种优化技术。如果编译器通过逃逸分析发现加锁的对象不会被其他线程访问,那么就可以消除该锁,从而避免不必要的同步操作,提升性能。

第七部分:JVM 常见面试问题总结

7.1 JVM 高频面试问题

在 Java 面试中,JVM 是一个非常常见的考察点。以下是一些常见的 JVM 面试问题,涵盖 JVM 的内存模型、垃圾回收机制、类加载器等多个方面。这些问题不仅要求面试者对 JVM 的工作原理有深刻的理解,还需要有实际调优和问题排查的经验。

1. JVM 的内存结构是什么样的?
  • 回答思路:
    • JVM 内存划分为方法区(Java 8 后称为元空间)、堆、虚拟机栈、程序计数器、本地方法栈五个区域。
    • 堆内存用于存储对象实例,分为新生代和老年代。虚拟机栈保存每个线程的局部变量、操作数栈等。
    • 方法区存储类信息、常量、静态变量、即时编译代码。程序计数器记录当前线程的执行位置。
2. JVM 中堆和栈的区别是什么?
  • 回答思路:
    • 堆用于存储所有对象实例和数组,属于线程共享区域,垃圾回收器会在堆中回收不再使用的对象。
    • 栈用于存储线程的局部变量、方法调用链信息,每个线程都有自己的栈。栈内存较小,生命周期与线程一致。
3. 你对垃圾回收机制了解多少?可以介绍一下不同的垃圾回收器吗?
  • 回答思路:
    • 垃圾回收器通过追踪和清除不可达对象来释放内存,常用的垃圾回收算法包括标记-清除、复制、标记-整理等。
    • 常见的垃圾回收器有 Serial、Parallel、CMS 和 G1。Serial 单线程执行垃圾回收,Parallel 使用多线程执行,CMS 适用于低停顿的应用,G1 适用于大堆内存的服务端应用。
4. Full GC 发生的原因是什么?如何优化避免 Full GC?
  • 回答思路:
    • Full GC 是对整个堆内存(包括新生代和老年代)进行的垃圾回收操作,通常由老年代内存不足、方法区溢出等原因触发。
    • 优化方法包括:增大堆内存大小,减少对象的频繁创建和过长生命周期,调整垃圾回收器设置如 G1 或 CMS,合理设置元空间大小。
5. 双亲委派模型的作用是什么?有遇到过需要打破双亲委派模型的情况吗?
  • 回答思路:
    • 双亲委派模型规定类加载请求会优先委派给父类加载器,以保证核心类库不会被重复加载或篡改。
    • 常见的打破双亲委派模型的场景有 SPI 机制,它需要使用线程上下文类加载器加载自定义的服务实现。
6. 类加载过程有哪些步骤?
  • 回答思路:
    • 类加载分为加载、验证、准备、解析、初始化五个步骤。
    • 加载阶段通过类加载器将字节码加载到内存;验证阶段确保类的合法性;准备阶段为类的静态变量分配内存;解析阶段将符号引用替换为直接引用;初始化阶段执行静态代码块和静态变量赋值。
7. 如何排查 OutOfMemoryError
  • 回答思路:
    • OutOfMemoryError 可能发生在堆、方法区或虚拟机栈中。常见原因有对象过多、类加载过多或栈深度过大。
    • 使用 jmap 生成堆快照,通过 MAT 分析内存泄漏问题;通过 -XX:MaxMetaspaceSize 控制元空间大小;通过 -Xss 调整栈大小。
8. 什么是逃逸分析?它有什么用?
  • 回答思路:
    • 逃逸分析是 JIT 编译器的优化技术,用于判断对象是否逃逸出当前方法。如果对象未逃逸,JVM 可以将其分配在栈上,避免在堆中分配对象。
    • 优点是减少堆内存分配和垃圾回收开销,并且提高对象的访问速度。
9. 什么是内存屏障?在 JVM 中它有什么作用?
  • 回答思路:
    • 内存屏障是一种指令,用于禁止 CPU 的指令重排序。它确保在多线程环境下,某些操作(如读写共享变量)具有可见性和有序性。
    • 在 JVM 中,volatile 关键字可以通过内存屏障确保变量的可见性和有序性。
10. 什么是类卸载?它发生在什么情况下?
  • 回答思路:
    • 类卸载是指 JVM 从内存中移除不再使用的类,通常在不再需要加载的类被垃圾回收器回收时发生。
    • 类卸载主要发生在自定义类加载器加载的类上,当类加载器和其加载的类都没有被引用时,类可以被卸载。
11. Java 8 中永久代的变化?为什么 Java 8 使用元空间代替了永久代?
  • 回答思路:
    • 在 Java 8 中,永久代被移除,取而代之的是元空间。永久代用于存储类的元数据和静态变量等,但容易导致内存溢出。
    • 元空间不使用堆内存,而是直接使用本地内存,从而可以动态调整大小,避免永久代内存溢出的情况。
12. 什么是 JVM 调优?你有实际 JVM 调优的经验吗?
  • 回答思路:
    • JVM 调优是通过调整 JVM 参数和配置来优化 Java 应用的性能。常见调优包括堆内存大小的调整、垃圾回收器的选择、Full GC 频率的控制、线程调度优化等。
    • 实际调优经验可以包括通过 GC 日志分析性能问题、使用工具(如 jmapjstack、VisualVM)来排查内存和线程问题,以及如何根据业务需求配置合适的 JVM 参数。
7.2 综合性 JVM 问题场景分析
  1. 场景一:大型电商系统的 JVM 调优实践

    • 问题:系统在高并发情况下频繁发生 Full GC,导致响应时间延迟。
    • 分析:通过查看 GC 日志,发现堆内存不足导致频繁的 Full GC。通过增加堆大小(-Xms-Xmx),并使用 G1 垃圾回收器替代 CMS,减少了 Full GC 的次数。此外,减少了长生命周期对象的使用,降低了老年代的占用。
  2. 场景二:内存泄漏问题排查

    • 问题:某线上服务随着运行时间增长,内存不断增加,最终抛出 OutOfMemoryError
    • 分析:使用 jmap 生成堆快照,通过 MAT 工具分析堆内存,发现某个静态集合类没有清理不再使用的对象,导致了内存泄漏。解决方案是在代码中定期清理该集合,释放不再需要的对象。
  3. 场景三:多线程应用的 JVM 栈溢出

    • 问题:在处理复杂业务逻辑时,系统抛出 StackOverflowError
    • 分析:由于业务逻辑存在深度递归调用,导致栈深度超出默认值。通过调整 JVM 栈大小参数(-Xss),增加每个线程的栈空间,解决了栈溢出问题。
7.3 常见陷阱与误区
  1. 误区一:JVM 参数设置越大越好

    • 解释:并不是堆内存、栈内存设置得越大越好。过大的堆会导致垃圾回收耗时较长,栈内存设置过大则可能浪费系统资源,甚至引发系统崩溃。应根据应用实际需求来设置合理的 JVM 参数。
  2. 误区二:Full GC 触发时 JVM 会立即回收所有对象

    • 解释:Full GC 是回收整个堆内存,但并不能保证所有对象都被立即回收。如果对象之间存在复杂的引用链,或者引用关系没有正确处理,可能导致对象仍然存活。
  3. 误区三:永久代溢出等同于堆内存溢出

    • 解释:永久代溢出和堆内存溢出是两

种不同的错误。永久代溢出与类的元数据、静态变量等有关,堆内存溢出则是由于对象实例数量过多导致堆空间不足。在 Java 8 之后,永久代已被元空间替代。
在这里插入图片描述

相关文章:

JVM面试知识点手册

第一部分&#xff1a;JVM 概述 1.1 JVM 简介 Java Virtual Machine&#xff08;JVM&#xff09; 是 Java 语言的核心组件&#xff0c;负责将 Java 程序编译后的字节码&#xff08;bytecode&#xff09;转换为机器指令&#xff0c;并在目标机器上执行。JVM 提供了硬件和操作系…...

Vue3.0组合式API:使用reactive()、ref()创建响应式代理对象

Vue3.0组合式API系列文章&#xff1a; 《Vue3.0组合式API&#xff1a;setup()函数》 《Vue3.0组合式API&#xff1a;使用reactive()、ref()创建响应式代理对象》 《Vue3.0组合式API&#xff1a;computed计算属性、watch监听器、watchEffect高级监听器》 《Vue3.0组合式API&…...

kubernetes调度2

1、各种缩写的应用 [rootk8s-master test]# kubectl get rsNAME DESIRED CURRENT READY AGEtest001-64c7957b5c 2 2 2 8m59stest001-698b98bb8f 0 0 0 12m[rootk8s-master test]# kubectl get replicas…...

Android中如何处理运行时权限?

在Android中&#xff0c;处理运行时权限是开发过程中一个至关重要的环节&#xff0c;它自Android 6.0&#xff08;API级别23&#xff09;引入&#xff0c;旨在提高用户隐私保护和应用的透明度。以下将详细阐述Android中处理运行时权限的方法、步骤、注意事项以及相关的最佳实践…...

【PyCharm】PyCharm:让开发者效率倍增的编程利器

在现代软件开发过程中&#xff0c;选择一款得心应手的编程工具不仅能提高效率&#xff0c;还能让编程过程更加愉悦。而在众多集成开发环境&#xff08;IDE&#xff09;中&#xff0c;PyCharm无疑是Python开发者的首选之一。作为一款功能强大、设计精良的IDE&#xff0c;PyCharm…...

Spring Boot- 配置中心问题

Spring Boot 配置中心相关问题探讨 在现代微服务架构中&#xff0c;随着系统规模的扩展和复杂度的增加&#xff0c;配置管理变得越来越重要。每个微服务都可能有大量的配置文件&#xff0c;包括数据库连接信息、缓存配置、消息队列配置等。如果每个服务独立管理配置文件&#…...

字符串专题-1

目录 1.简介 2.例题 2.1找出字符串第一个匹配项的下标 2.2最长公共前缀 2.3最长回文子串 2.4二进制求和 2.5字符串相乘 1.简介 关于字符串匹配的常用算法KMP&#xff0c;我这里只做思路上的说明&#xff0c;具体内容文字和图片写来写去还是有点怪异&#xff0c;这边推荐…...

Unsupervised Deep Representation Learning for Real-Time Tracking

摘要 我们的无监督学习的动机是稳健的跟踪器应该在双向跟踪中有效。具体来说&#xff0c;跟踪器能够在连续帧中前向定位目标对象&#xff0c;并回溯到其在第一帧中的初始位置。基于这样的动机&#xff0c;在训练过程中&#xff0c;我们测量前向和后向轨迹之间的一致性&#xf…...

第二讲 数据结构

单链表 826. 单链表 - Acwing题库 数据结构&#xff1a; e[N]&#xff1a;用于存储节点的值的数组。ne[N]&#xff1a;作为“下一个”指针的数组&#xff0c;用于连接节点。head&#xff1a;指向链表头部的索引。idx&#xff1a;当前可用的下一个索引。 初始化&#xff1a; …...

docker部署excalidraw画图工具

0&#xff09;效果 0.1&#xff09;实时协作 0.2&#xff09;导出格式 1&#xff09;docker安装 docker脚本 bash <(curl -sSL https://cdn.jsdelivr.net/gh/SuperManito/LinuxMirrorsmain/DockerInstallation.sh)docker-compose脚本 curl -L "https://github.com/…...

5G技术对IT行业的影响及未来发展

5G技术对IT行业的影响及未来发展 随着5G网络的快速部署&#xff0c;全球正进入一个全新的高速连接时代。5G不仅仅是移动通信的升级&#xff0c;它将带来更多的应用场景和改变各个行业的运作方式。本文将探讨5G技术的核心特点、对IT行业的影响&#xff0c;以及未来可能的发展方向…...

字节跳动的微服务独家面经

在之前的文章中也介绍了相关微服务的项目开发知识&#xff0c;那么在本文中我将分享一份来自字节跳动相关岗位的面试经历&#xff0c;在其中我们一起来看看面试问题的详细内容&#xff0c;如果有对微服务的感兴趣的朋友们也可以联系我了解我们的微服务项目&#xff0c;也希望该…...

嵌套函数的例子(TypeScript)

在 TypeScript 中&#xff0c;嵌套函数是指在一个函数内部定义另一个函数。嵌套函数可以访问外部函数的变量&#xff08;闭包&#xff09;&#xff0c;并且可以在内部进行调用。下面是一个简单的例子来说明嵌套函数的使用&#xff1a; function outerFunction(outerVariable: …...

0915,SOCKET网络编程部分,三种I/O多路复用模型(select ,poll,epoll)

目录 nc 127.0.0.1 port 01_socket_client.cc 01_socket_server.cc 02_select_client.cc 02_select_server.cc 03_poll_server.cc 04_epoll_server.cc 01_socket_client.cc #include <stdlib.h> #include <string.h> #include <sys/stat.h> #inclu…...

HarmonyOS 应用获取公钥和 MD5 指纹签名信息

鸿蒙版本获取 MD5 指纹和公钥可参考如下方式; 首先,通过 AGC 官网 将所需证书下载至本地; 其次,通过记事本或者文本编译器的方式将其正式打开,将其内容中前两项 BEGIN CERTIFICATE 和 END CERTIFICATE 的段落删除,仅保留最后一段中的内容(包括 BEGIN CERTIFICATE 和 END CERTI…...

封装一个录音声音振动效果的组件

目标&#xff1a;根据声音的大小实现声音振动特效 实现步骤&#xff1a; 通过 getAudioCapturerMaxAmplitude 观察音频区间封装振动组件&#xff0c;通过声音振幅数据实现振动效果 落地代码&#xff1a; 1&#xff09;获取振幅数据&#xff0c;出入振动组件 AudioPage.ets …...

Java、JS与Go的扩展操作符,揭秘它们的‘魔法’!

在这个快节奏的互联网时代&#xff0c;程序员们总是希望能够用更简洁、更高效的方式来编写代码。扩展操作符&#xff08;Spread Operator&#xff09;是 JavaScript ES6 引入的重要特性&#xff0c;而 Java 和 Go 也有各自的方式来实现类似的功能。今天&#xff0c;我们就来深入…...

ROS学习笔记13——rosbag功能包的简单使用

rosbag是用于录制和回放 ROS 主题的一个工具集&#xff0c;实现了数据的复用&#xff0c;方便调试和测试。rosbag本质也是ros的节点&#xff0c;当录制时&#xff0c;rosbag是一个订阅节点&#xff0c;可以订阅话题消息并将订阅到的数据写入磁盘文件&#xff1b;当重放时&#…...

Python Flask网页开发基本框架

注&#xff1a;Flask详细学习请见Flask学习合集。 直接上代码: app.py from flask import Flaskapp Flask(__name__)app.route("/") def hello():return "Hello, World!"if __name__ "__init__":app.run(host "127.0.0.1", port…...

Mybatis-plus进阶篇(五)

文章目录 条件构造器补充知识TypeHandlerWrappers示例&#xff1a; 线程安全性示例&#xff1a; 使用 Wrapper 自定义 SQL示例&#xff1a;使用方法 使用注解查询使用XML配置查询链式调用与Lambda式调用 条件构造器补充知识 TypeHandler 在 wrapper 中使用 typeHandler 需要特…...

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…...

Java 语言特性(面试系列2)

一、SQL 基础 1. 复杂查询 &#xff08;1&#xff09;连接查询&#xff08;JOIN&#xff09; 内连接&#xff08;INNER JOIN&#xff09;&#xff1a;返回两表匹配的记录。 SELECT e.name, d.dept_name FROM employees e INNER JOIN departments d ON e.dept_id d.dept_id; 左…...

Leetcode 3576. Transform Array to All Equal Elements

Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接&#xff1a;3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到&#xf…...

日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする

日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする 1、前言(1)情况说明(2)工程师的信仰2、知识点(1) にする1,接续:名词+にする2,接续:疑问词+にする3,(A)は(B)にする。(2)復習:(1)复习句子(2)ために & ように(3)そう(4)にする3、…...

解决Ubuntu22.04 VMware失败的问题 ubuntu入门之二十八

现象1 打开VMware失败 Ubuntu升级之后打开VMware上报需要安装vmmon和vmnet&#xff0c;点击确认后如下提示 最终上报fail 解决方法 内核升级导致&#xff0c;需要在新内核下重新下载编译安装 查看版本 $ vmware -v VMware Workstation 17.5.1 build-23298084$ lsb_release…...

JVM垃圾回收机制全解析

Java虚拟机&#xff08;JVM&#xff09;中的垃圾收集器&#xff08;Garbage Collector&#xff0c;简称GC&#xff09;是用于自动管理内存的机制。它负责识别和清除不再被程序使用的对象&#xff0c;从而释放内存空间&#xff0c;避免内存泄漏和内存溢出等问题。垃圾收集器在Ja…...

【 java 虚拟机知识 第一篇 】

目录 1.内存模型 1.1.JVM内存模型的介绍 1.2.堆和栈的区别 1.3.栈的存储细节 1.4.堆的部分 1.5.程序计数器的作用 1.6.方法区的内容 1.7.字符串池 1.8.引用类型 1.9.内存泄漏与内存溢出 1.10.会出现内存溢出的结构 1.内存模型 1.1.JVM内存模型的介绍 内存模型主要分…...

在鸿蒙HarmonyOS 5中使用DevEco Studio实现指南针功能

指南针功能是许多位置服务应用的基础功能之一。下面我将详细介绍如何在HarmonyOS 5中使用DevEco Studio实现指南针功能。 1. 开发环境准备 确保已安装DevEco Studio 3.1或更高版本确保项目使用的是HarmonyOS 5.0 SDK在项目的module.json5中配置必要的权限 2. 权限配置 在mo…...

rm视觉学习1-自瞄部分

首先先感谢中南大学的开源&#xff0c;提供了很全面的思路&#xff0c;减少了很多基础性的开发研究 我看的阅读的是中南大学FYT战队开源视觉代码 链接&#xff1a;https://github.com/CSU-FYT-Vision/FYT2024_vision.git 1.框架&#xff1a; 代码框架结构&#xff1a;readme有…...

DAY 45 超大力王爱学Python

来自超大力王的友情提示&#xff1a;在用tensordoard的时候一定一定要用绝对位置&#xff0c;例如&#xff1a;tensorboard --logdir"D:\代码\archive (1)\runs\cifar10_mlp_experiment_2" 不然读取不了数据 知识点回顾&#xff1a; tensorboard的发展历史和原理tens…...