JVM面试知识点手册

news2024/11/16 4:34:45

第一部分: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 之后,永久代已被元空间替代。
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2151129.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

创客匠人案例故事|闭关 20 天,私域大爆发,高额发售秘诀是什么?

不是你的能力决定了你的命运&#xff0c;而是你的决定改变了你的人生 王龙老师心赏教养法创始人心赏家园家庭“心生态”发起人国家二级心理咨询师 他是一名致力于解决家庭困境的老师&#xff0c;通过心赏转化五步法&#xff0c;帮助身陷家庭困境的父母&#xff0c;解决自我关系…

Linux——k8s认识

计算资源隔离 - 更方便进行高并发架构的维护和升级 - 架构管理的灵活性更高&#xff0c;不再以单个节点的物理资源作为基础 技术&#xff1a; - 硬件辅助虚拟化 - 容器技术 在企业部署方案中&#xff0c;很少以单节点实现虚拟化和容器技术&#xff0c;一般以集群状态来运…

翻唱技巧:AU和Cubase翻唱录制对轨技巧

分享和记录一下个人翻唱的经验和技巧&#xff01;防止后续自己忘了&#xff01;同时如果有大佬看到&#xff0c;希望可以帮我指出其中的错误&#xff01;个人推荐用Cubase12录制翻唱&#xff0c;因为Cubase12可以做乐段的标记&#xff0c;翻唱时有助于学习一些歌曲的层次设计。…

C++对象拷贝时的优化编译

在现代编译器中&#xff0c;当我们在 C中进行对象的拷贝操作时&#xff0c;编译器并非只是机械地执行逐字节的复制。相反&#xff0c;它会进行优化&#xff0c;避免不必要的拷贝构造等等&#xff0c;这种优化包括“返回值优化”&#xff08;RVO&#xff09;&#xff0c;“拷贝省…

JavaSE——String类

一、字符串构造 注意&#xff1a;String是引用类型&#xff0c;内部并不存储字符串本身。 有三种方式&#xff1a; public class Test1 {public static void main(String[] args) {// 使用常量串构造String s1 "hello java";System.out.println(s1);// 直接newSt…

JavaEE:探索网络世界的魅力——玩转UDP编程

文章目录 UDPUDP的特点UDP协议端格式校验和前置知识校验和具体是如何工作的? UDP UDP的特点 UDP传输的过程类似于寄信. 无连接: 知道对端的IP和端口号就直接进行传输,不需要建立连接.不可靠: 没有确认机制,没有重传机制,如果因为网络故障导致该段无法到达对方,UDP协议也不会…

Linux:make,Makefile

hello&#xff0c;各位小伙伴&#xff0c;本篇文章跟大家一起学习《Linux&#xff1a;make&#xff0c;Makefile》&#xff0c;感谢大家对我上一篇的支持&#xff0c;如有什么问题&#xff0c;还请多多指教 &#xff01; 如果本篇文章对你有帮助&#xff0c;还请各位点点赞&…

技术成神之路:设计模式(十四)享元模式

介绍 享元模式&#xff08;Flyweight Pattern&#xff09;是一种结构性设计模式&#xff0c;旨在通过共享对象来有效地支持大量细粒度的对象。 1.定义 享元模式通过将对象状态分为内部状态&#xff08;可以共享&#xff09;和外部状态&#xff08;不可共享&#xff09;&#xf…

为什么我不建议你考CCNA/HCIA这种初级认证

CCNA和HCIA&#xff0c;这两个证书可能是许多刚入门的网络工程师第一个听说的认证。作为入门级网络认证&#xff0c;CCNA是思科公司推出的&#xff0c;提供了网络基础的全面知识&#xff0c;而HCIA则是华为的基础认证&#xff0c;覆盖了网络技术的基本原理。 这些认证的目的是帮…

2023-03 GESP Python一级试卷

一、单选题&#xff08;每题 2 分&#xff0c;共 30 分&#xff09; 1.以下不属于计算机输入设备的有&#xff08; &#xff09;。 ​ A&#xff0e;键盘 ​ B&#xff0e;音箱 ​ C&#xff0e;鼠标 ​ D&#xff0e;传感器 2.计算机系统中存储的基本单位用 B 来表示&am…

用友U8二次开发工具KK-FULL-*****-EFWeb使用方法

1、安装: 下一步&#xff0c;下一步即可。弹出黑框不要关闭&#xff0c;让其自动执行并关闭。 2、服务配置&#xff1a; 输入服务器IP地址&#xff0c;选择U8数据源&#xff0c;输入U8用户名及账号&#xff0c;U8登录日期勾选系统日期。测试参数有效性&#xff0c;提示测试通过…

洞悉市场先机,Vatee万腾平台助力企业精准决策

在瞬息万变的商业环境中&#xff0c;每一个市场动向都可能成为企业兴衰的关键。因此&#xff0c;洞悉市场先机&#xff0c;做出精准决策&#xff0c;成为了企业持续发展和保持竞争力的核心要素。Vatee万腾平台&#xff0c;凭借其强大的数据分析能力和智能化技术&#xff0c;正成…

对象检测边界框损失 – 从IOU到ProbIOU

1.概述 目标检测损失函数的选择在目标检测问题建模中至关重要。通常&#xff0c;目标检测需要两个损失函数&#xff0c;一个用于对象分类&#xff0c;另一个用于边界框回归&#xff08;BBR&#xff09;。本文将重点介绍 IoU 损失函数&#xff08;GIoU 损失、DIoU 损失和 CIoU 损…

【补码运算】设x=FEH,当x分别为下列情况时写出其对应的十进制真值(写出计算过程):(1)为原码(2)为补码。

欢迎来到我的技术博客&#xff01; &#x1f389; 这里不仅有满满的编程干货和学习资源&#xff0c;我的某站账号也为你准备了更多实用的技术视频和知识分享。 &#x1f449; 点击关注我的小破站账号&#xff0c;获取更多编程技巧和学习资源&#xff01; 小破站主页 题目&…

搜索引擎onesearch3实现解释和升级到Elasticsearch v8系列(三)-文档

文档 文档服务负责写入&#xff0c;包括批量&#xff1b;id获取文档&#xff1b;nested写入 写入文档 写入文档主要是构建IndexRequest&#xff0c;索引请求 Elasticsearch v8构建文档索引请求简单很多&#xff0c;可以直接接受Map数据 批量写入文档 批量操作可以融合增删改…

肥胖成因:饮食之外,消耗吸收慢是关键因素

肥胖问题一直被现代社会所关注&#xff0c;不可否认&#xff0c;饮食是影响胖瘦的重要因素之一。高热量、高油脂的食物摄入过多&#xff0c;也确实会导致热量油脂过剩&#xff0c;堆积储存进身体内进而养肥身体。可在正常情况中&#xff0c;就算是消耗吸收率一般的人&#xff0…

从源码看透 Ptmalloc:堆内存分配与释放的背后

&#x1f6f8;0. 前言 内存管理是个很复杂的东西&#xff0c;一般的程序员在写应用层代码的时候根本就不会关注内存的使用&#xff0c;尤其是 GC 语言自带垃圾回收机制&#xff0c;很多同学都使用过 C 语言中的 malloc&#xff0c;但很少有人能知道 malloc 其实是个很复杂的实现…

tomcat服务搭建部署ujcms网站

tomcat服务搭建部署ujcms网站 关闭selinux和防火墙 setenforce 0 && systemctl stop firewalld安装java环境 #卸载原有java8环境 yum remove java*#上传java软件包&#xff0c;并解压缩 tar -xf openjdk-11.0.1_linux-x64_bin.tar.gz && mv jdk-11.0.1 jdk11…

Android Perfetto 学习

1、如何抓取性能日志 方式1、通过手机里的System Tracing抓取 1、点击Settings->System->Developer options->System Tracing->Record trace 打开 2、操作完成后&#xff0c;点击Settings->System->Developer options->System Tracing->Record trace…

C++自动寻径算法

测试 #include <iostream> #include "source/AStar.hpp"int main() {AStar::Generator generator;generator.setWorldSize({25, 25});generator.setHeuristic(AStar::Heuristic::euclidean);generator.setDiagonalMovement(true);generator.addCollision({1, …