JVM Notes

JVM 概述

Java Virtual Machine (JVM) 是 Java 语言的运行环境,负责加载、验证、解释和执行 Java 字节码。

.class 文件通过 JVM 转换到各个平台可以执行的机器指令。

JVM 结构

JVM 由类加载系统、运行时数据区和执行引擎构成。

类加载系统

类加载系统首先读取指定的类文件,并遵循双亲委派机制进行加载。

双亲委派机制

  1. 类加载请求

    • 当某个类加载器(如应用类加载器)收到加载某个类的请求时,它不会直接尝试加载该类。
  2. 向父加载器委派

    • 它会先将这个请求委派给它的父类加载器。
    • 父类加载器再继续向它的父类加载器委派,直到到达顶层的 Bootstrap ClassLoader(启动类加载器)。
  3. 加载类

    • 顶层的 Bootstrap ClassLoader 会尝试加载该类。
    • 如果它无法加载,则将控制权交还给子加载器,依次向下传递,直到找到能够加载该类的加载器。

加载顺序

  1. Bootstrap ClassLoader(启动类加载器)
    • 加载核心类库(如 rt.jar 中的类)。
  2. Extension ClassLoader(扩展类加载器)
    • 加载扩展类库(如 jre/lib/ext 目录下的类)。
  3. Application ClassLoader(应用类加载器)
    • 加载应用程序的类(如 classpath 下的类)。
graph TD
    A["BootStrapClassLoader(引导类加载器)"]
    B["ExtensionClassLoader(扩展类加载器)"]
    C["AppClassLoader(应用类加载器)"]
    CC["ClassNotFoundException(类未找到异常)"]

    D{"加载过?"}
    D1{"加载过?"}
    E{"可以加载"}
    E1{"可以加载"}
    E2{"可以加载"}

    C -->D1
    D1 --> B
    B --> D
    D --> A
    
    A --> E
    E --> B
    B --> E1
    E1 --> C
    C --> E2
    E2 --> CC

类加载过程

  1. 加载

    • 将文件中的常量池、字段、方法和指令加载到 JVM 内存的共享区域方法区中。
  2. 验证

    • 验证类文件的字节码是否符合 JVM 规范,确保类的正确性和安全性。
  3. 准备

    • 为类的静态变量分配内存空间,并设置默认值(如 0、null 等)。
  4. 解析

    • 常量池中表示对象的符号引用指向到实际的内存地址,也就是直接引用。
  5. 初始化

    • 执行类的静态代码块和静态变量的初始化。

执行引擎

工作模式

  1. 解释执行:逐条解释执行字节码指令。
  2. 即时编译(JIT):将热点代码编译为本地机器码,并缓存以提高执行效率。

主方法执行

  • 静态代码块执行完成后,JVM 会继续执行主方法(main method)。
  • 如果没有 main 方法,JVM 会报错。

调用 main 方法时,会在内存中分配线程私有的空间,包括程序计数器和栈帧。

内存管理

堆内存分代设计

Q3: 堆内存为什么要分代设计?

A3: 堆内存分代设计是为了提高垃圾回收的效率。根据对象的生命周期,将堆内存分为新生代和老年代。新生代中的对象大多数是短命的,垃圾回收频繁进行,而老年代中的对象相对长命,垃圾回收较少。通过分代收集,可以针对不同生命周期的对象采用不同的回收策略,从而提高整体性能。

对象内存分配过程

  1. 内存分配:在堆中为对象分配内存。
  2. 初始化:将对象的实例变量设置为默认值(如 0、null 等)。
  3. 执行构造函数:调用对象的构造函数,执行初始化逻辑。
  4. 返回对象引用:将对象的引用返回给调用者。

内存分配策略

  1. 碰撞指针法

    • 通过维护一个指针来指向当前可用内存的起始位置。
    • 分配内存时,直接从指针位置分配所需大小的内存,然后更新指针位置。
    • 优点:分配速度快。
    • 缺点:容易导致内存碎片。
  2. 空闲法

    • 通过维护一个空闲内存列表来管理内存分配。
    • 分配内存时,遍历空闲列表找到足够大的空闲块进行分配,并更新空闲列表。
    • 优点:减少内存碎片。
    • 缺点:分配速度相对较慢。

垃圾回收

后续流程

实例初始化后,会将对象的引用存储到局部变量表中(虚拟机栈),线程可以通过引用访问到该对象。后续的代码会继续按照这个流程工作。