• 类的加载机制 :加载、链接(验证、准备、解析)初始化、使用、卸载。类加载器分类及其应用程序类加载器、自定义加载器。
  • jvm内存结构:程序计数器、堆、虚拟机栈、本地方法栈、方法区(Java8已移除)、元空间(Java8新增)的作用及基本原理
  • GC算法: 垃圾回收。对象存活判断
  • GC分析 :命令调优 JVM内存参数设置及调优

JVM 内存结构

Java虚拟机(JVM)的内存结构精心设计用来优化运行时性能并提供动态内存管理。以下是JVM内存结构的关键部分:

  1. 堆(Heap):
    • 堆是JVM内存中主要的运行时数据区域,用于存储对象实例和数组。
    • 它在JVM启动时创建,并且是所有线程共享的内存区域。
    • 堆内存被分为三个主要区域:年轻代(Young Generation),老年代(Old Generation)或称为老年区(Tenured Generation),以及永久代(PermGen,在Java 8中已被元空间(Metaspace)取代)。
  2. 方法区(Method Area):
    • 方法区(在Java 8及之后版本中已被Metaspace取代)是另一个线程共享的内存区域,用于存储已被JVM加载的类信息,常量,静态变量,即时编译器编译后的代码等。
    • 在Java 8中,Metaspace主要用本地内存(即非JVM内存)来存储类元数据,使得其可以动态扩展,不再受到JVM内存大小的限制。
  3. 栈(Stack):
    • JVM为每个线程创建一个栈,用来存储局部变量、操作数栈、动态链接和方法返回值等。
    • 每个方法调用都会创建一个栈帧,用于存储局部变量表、操作数栈、方法出口等信息。
  4. 程序计数器(Program Counter, PC):
    • 程序计数器是一小块内存空间,它可以看作是当前线程所执行的字节码的行号指示器。
    • 在任何时刻,一个CPU(或者说是一个JVM线程)都只会执行一个方法的代码,程序计数器会保持当前线程正在执行的Java方法的JVM指令地址。
    • 如果执行的是本地方法(Native Method),这个计数器的值则为空(undefined)。
  5. 本地方法栈(Native Method Stack):
    • 本地方法栈和Java栈类似,但它们服务于本地方法调用(即不是由Java编写的方法,而是其他语言如C或C++的方法)。
    • 本地方法栈也可以使用C堆栈,这取决于JVM的实现。
  6. 直接内存(Direct Memory):
    • 直接内存并不是JVM运行时数据区的一部分,但经常被Java程序用来高效进行IO操作,因为它可以通过NIO(New Input/Output)库来直接对文件进行操作,避免了在Java堆和本地IO缓冲区之间复制数据的开销。

JVM的内存管理包括垃圾回收(GC),它定期检查这些内存区域,识别和清除不再使用的对象,释放内存以便再次利用。JVM的内存模型是设计成多线程环境下高效执行Java程序的重要组成部分,使得Java应用能够运行在各种不同的硬件和操作系统平台上。

垃圾回收机制

JVM 的垃圾回收(Garbage Collection,GC)机制是自动管理内存的过程,它旨在帮助开发者无需手动释放分配的内存。GC 进程会跟踪在堆上创建的每个对象,确定哪些对象不再被使用,并释放这些不再需要的对象占用的内存空间。以下是 JVM 垃圾回收机制的基本概念和策略:

  1. 可达性分析(Reachability Analysis):
    • GC 通过一系列称为 “GC Roots” 的对象作为起始点,跟踪引用链。
    • 如果一个对象从 GC Roots 开始无法到达,那么这个对象被认为是不可达的,因此是可回收的。
  2. 垃圾回收算法:
    • 标记-清除(Mark-Sweep): 首先标记所有从 GC Roots 可达的对象,然后清除未标记的对象。
    • 标记-整理(Mark-Compact): 在标记阶段后,移动所有存活的对象,使它们在内存中连续排列,以解决内存碎片问题。
    • 复制(Copying): 将内存划分为两块,每次只使用一块。在垃圾回收时,将存活的对象从当前使用的内存块复制到另一块,然后清空当前块中的所有对象。
    • 分代收集(Generational Collection): 基于对象存活期的不同,将堆分为新生代和老年代,并使用不同的策略进行垃圾回收。
  3. 分代垃圾回收:
    • JVM 堆内存被分为几个不同的区域,主要是新生代(Young Generation)和老年代(Old Generation)。
    • 新生代: 新创建的对象首先放在新生代。由于许多对象生命周期短暂,新生代通常使用复制算法,分为 Eden 空间和两个 Survivor 空间(S0 和 S1)。
    • 老年代: 存活过一定垃圾回收周期的对象会被晋升到老年代。老年代一般使用标记-清除或标记-整理算法,因为对象的存活率高,复制算法效率低。
    • 永久代/元空间(Java 8及以后): 用于存储类元数据和方法信息。在 Java 8 中,这部分内存已从 JVM 移至直接内存区域,称为元空间。
  4. 垃圾回收器:
    • JVM 提供了多种垃圾回收器,每个回收器有其特定的使用场景和特性。包括但不限于:Serial GC、Parallel GC、CMS GC、G1 GC 和 ZGC。
    • 开发者可以根据应用的需求和垃圾回收的性能特点选择适当的垃圾回收器。
  5. 垃圾回收时机:
    • 垃圾回收并不是实时进行的,JVM 会根据内存使用情况、分配速率以及提供给 JVM 的参数等来决定何时执行垃圾回收。
  6. 性能考量:
    • GC 可能会造成应用暂停,特别是在执行全堆垃圾回收(Full GC)的时候。不同的垃圾回收器和参数配置会影响暂停时间的长度和频率。

理解 JVM 的垃圾回收机制对于编写高效的 Java 应用、调优性能以及避免内存泄漏是非常重要的。通过合理的 JVM 参数设置和选择适合应用负载特点的垃圾回收器,可以显著提高应用的性能和响应能力。

GC的时机

JVM 的垃圾回收(GC)的时机并不是固定的,而是由多种因素决定的,包括堆内存的使用情况、垃圾回收器的类型、以及运行时的性能目标等。以下是触发 JVM 垃圾回收的几种常见情况:

  1. 堆内存不足:
    • 当堆内存中的可用空间不足以为新对象分配空间时,JVM 会触发垃圾回收以释放内存。
    • 如果垃圾回收后仍然没有足够的空间进行分配,可能会抛出 OutOfMemoryError。
  2. 系统调用:
    • 程序可以通过调用 System.gc() 来建议 JVM 执行垃圾回收,但是这只是一个建议,JVM 可以忽略这个调用。
  3. 老年代空间不足:
    • 当老年代接近填满时,为了给旧的对象腾出空间,JVM 会触发一次全堆回收,也称为 Major GC 或 Full GC。
  4. 新生代空间不足:
    • 新生代(Young Generation)在填满之后会触发 Minor GC,这种回收通常比 Full GC 更频繁且更快速,因为它只回收存活时间较短的对象。
  5. 元空间不足(Java 8 及以上版本):
    • 如果元空间(Metaspace)中用于存储类元数据的空间不足,也会触发垃圾回收。在 Java 8 之前,这个区域被称为永久代(PermGen)。
  6. JVM 即时编译器的请求:
    • JVM 的即时编译器(Just-In-Time, JIT)有时会请求进行垃圾回收以获取关于对象存活情况的统计信息,帮助优化代码。
  7. 内存碎片化:
    • 随着时间的推移,堆内存可能会出现碎片化。为了优化内存分配,JVM 可能会执行垃圾回收来压缩和整理内存。
  8. 主动内存管理策略:
    • JVM 的一些垃圾回收器提供了主动内存管理策略,例如 G1 GC。这些回收器会在保持应用吞吐量和响应时间的基础上,计划和执行垃圾回收。
  9. 分配率高:
    • 如果应用程序的对象分配率很高,可能导致新生代迅速填满,从而频繁触发 Minor GC。

理解触发垃圾回收的条件有助于更好地调优应用程序和JVM的性能。正确配置垃圾回收器和内存设置,可以最小化GC的影响,例如减少GC引起的停顿时间,以及避免内存溢出错误。开发人员应当根据应用程序的特性和性能需求来调整这些设置。