0%

JVM 类加载器分类与双亲委派机制

类加载器(ClassLoader)是 JVM 实现类加载机制的核心组件,负责将不同来源的 .class 文件加载到内存中。JVM 中类加载器分为默认类加载器自定义类加载器,它们通过 “双亲委派机制” 协同工作,确保类加载的安全性和一致性。本文将详细解析各类加载器的特点、职责,以及双亲委派机制的原理与应用。

默认类加载器:JVM 内置的加载器

JVM 提供了三种默认类加载器,各自负责特定路径的类加载,形成层级关系(非继承,而是委托关系)。

引导类加载器(Bootstrap ClassLoader)

  • 实现语言:由 C/C++ 编写(非 Java 代码),是 JVM 自身的一部分。
  • 加载范围:负责加载 JDK 核心类库,具体包括:
    • JAVA_HOME/jre/lib 目录下的核心 jar 包(如 rt.jarresources.jar 等,名称符合 JVM 识别规则的类库);
    • 由系统属性 sun.boot.class.path 指定的路径。
  • 特点:
    • 没有继承 java.lang.ClassLoader 类(是 JVM 内置组件);
    • 是所有类加载器的 “根加载器”,其他加载器的 “父加载器” 默认指向它;
    • 无法通过 Java 代码直接获取其实例(Class.getClassLoader() 对核心类返回 null,如 Object.class.getClassLoader() 返回 null)。

扩展类加载器(Extension ClassLoader)

  • 实现类sun.misc.Launcher$ExtClassLoader(Java 语言编写),继承 java.lang.ClassLoader
  • 加载范围:负责加载 Java 扩展类库,具体包括:
    • JAVA_HOME/jre/lib/ext 目录下的 jar 包;
    • 由系统属性 java.ext.dirs 指定的路径。
  • 父加载器:逻辑上的父加载器是引导类加载器(实际通过委托机制关联,而非继承)。
  • 作用:扩展 JDK 功能,用户可将自定义扩展类放入 ext 目录,由其自动加载。

系统类加载器(Application ClassLoader)

  • 实现类sun.misc.Launcher$AppClassLoader(Java 语言编写),继承 java.lang.ClassLoader
  • 加载范围:负责加载应用程序的类,具体包括:
    • classpath 环境变量指定的路径(可通过 -cp-classpath 命令行参数修改);
    • JAR 包中 Manifest 文件的 Class-Path 属性指定的路径。
  • 父加载器:扩展类加载器。
  • 特点:
    • 是程序默认的类加载器,应用中大多数类由它加载;
    • 可通过 ClassLoader.getSystemClassLoader() 方法获取其实例。
阅读全文 »

JVM 类加载机制:从字节码到运行的完整流程

类加载机制是 JVM 将 .class 字节码文件加载到内存,并对其进行验证、准备、解析和初始化,最终形成可执行代码的过程。这一机制是 JVM 实现 “跨平台” 和 “动态加载” 的核心,也是理解 Java 运行原理的关键。本文将详细解析类加载的全流程,包括加载、链接(验证、准备、解析)、初始化三个核心阶段,以及主动 / 被动使用、常见错误等关键知识点。

类加载机制概述

类加载机制的本质是将字节码数据转化为 JVM 可识别的运行时数据结构,并生成代表该类的 java.lang.Class 对象(作为访问类元数据的入口)。整个过程由类加载器子系统完成,分为三个主要阶段:
加载 → 链接(验证 → 准备 → 解析) → 初始化

这三个阶段按顺序执行,但解析阶段可能在初始化之后触发(为支持动态绑定)。

加载阶段:将字节码载入内存

加载是类加载的第一个阶段,核心任务是找到 .class 字节码文件并将其加载到 JVM 内存中。

加载的核心任务

  1. 获取字节码流:通过类的全限定名(如 com.example.User)查找并获取其二进制字节流(.class 文件)。
    字节码的来源多样:本地文件系统、网络(如 Applet)、压缩包(.jar/.war)、动态生成(如动态代理 Proxy 生成的类)、加密文件(反编译保护)等。
  2. 转换为运行时数据:将字节码流所代表的静态存储结构(如类的结构、字段、方法)转换为方法区的运行时数据结构(JVM 内部格式)。
  3. 生成 Class 对象:在堆内存中生成一个代表该类的 java.lang.Class 对象,作为方法区中该类元数据的访问入口(开发者可通过 Class.forName() 等方式获取此对象)。

加载的关键特点

  • 懒加载:类加载器仅在首次主动使用类时才加载(而非程序启动时一次性加载所有类),优化内存使用。
  • Class 对象的唯一性:一个类的 Class 对象在 JVM 中是唯一的(由类加载器和全限定名共同决定,即 “双亲委派模型” 保证)。

链接阶段:验证与准备运行环境

链接阶段是对加载的字节码进行处理,确保其符合 JVM 规范并为初始化做准备,分为验证、准备、解析三个子阶段。

阅读全文 »

JVM 生命周期:从启动到退出的完整历程

JVM(Java 虚拟机)作为运行字节码的虚拟进程,其生命周期遵循 “启动 - 执行 - 退出” 的完整流程。理解这一过程有助于深入掌握 JVM 的运行机制,以及排查与进程生命周期相关的问题(如进程异常退出、资源未释放等)。

JVM 启动:初始化与类加载

JVM 的启动是一个从无到有创建虚拟机进程的过程,核心是通过引导类加载器加载初始类,完成虚拟机的初始化。

1. 启动触发方式

  • 命令行启动:通过java命令启动(如java -jar app.jarjava com.example.Main),底层调用 JVM 的启动接口(如JNI_CreateJavaVM)。
  • 嵌入式启动:通过 JNI(Java Native Interface)在其他语言(如 C/C++)中嵌入 JVM,手动调用启动接口创建虚拟机实例。

2. 启动核心步骤

  1. 创建 JVM 实例:操作系统分配进程资源,初始化 JVM 的内部数据结构(如内存管理模块、线程管理模块)。
  2. 初始化引导类加载器:引导类加载器(Bootstrap ClassLoader)是 JVM 自带的加载器,负责加载 JVM 运行必需的核心类(如java.lang.Objectjava.lang.ClassLoader等,位于rt.jar中)。
  3. 加载初始类
    初始类由虚拟机实现指定(通常是用户程序的入口类,即包含main方法的类)。引导类加载器通过类的全限定名找到并加载该类,触发类加载的 “加载 - 链接 - 初始化” 流程(见前文)。
  4. 启动主线程:初始化完成后,JVM 创建主线程(main线程),执行初始类的main方法,标志着程序开始执行。

3. 启动阶段的关键特点

  • 启动过程依赖 JVM 的配置参数(如堆大小-Xms、垃圾回收器-XX:+UseG1GC等),这些参数会影响内存分配、执行引擎等核心组件的初始化。
  • 若初始类加载失败(如类不存在、字节码损坏),JVM 会在启动阶段抛出错误并终止。
阅读全文 »

Git 修改提交信息:从最新提交到历史提交的完整指南

在使用 Git 时,提交信息(commit message)是版本历史的重要说明,偶尔会出现拼写错误、信息不全等问题。无论是修改最近一次提交,还是调整历史提交记录,Git 都提供了相应的工具。本文将详细介绍不同场景下修改提交信息的方法。

修改最新一次提交信息(简单场景)

如果刚提交完就发现信息有误,直接使用 git commit --amend 即可快速修改,这是最常用的场景。

操作步骤:

  1. 执行修改命令

    1
    git commit --amend

    执行后会打开默认编辑器(如 Vim),显示当前最新提交的信息。

  2. 编辑提交信息

    • 在编辑器中修改错误内容(如将 “fix: 修 bug” 改为 “fix: 修复登录页面验证码失效问题”)。
    • 保存并退出编辑器(Vim 中按 Esc 后输入 :wq)。
  3. 完成修改
    修改后,Git 会生成一个新的 commit ID,覆盖原有的最新提交(旧提交会被自动清理)。

简化命令:

如果只需修改提交信息,无需修改提交内容,可直接在命令行指定新信息:

1
git commit --amend -m "新的提交信息"
阅读全文 »

Linux 中的 file 对象:进程视角下的已打开文件

在 Linux 系统中,file 对象(文件对象)是进程与文件交互的核心接口,代表进程已打开的文件实例。它与 inode、dentry 共同构成了文件操作的底层框架,但 file 对象更贴近进程的操作视角,记录了进程访问文件的动态状态。

file 对象的本质与核心作用

什么是 file 对象?

file 对象是 Linux 内核在内存中创建的一种数据结构,由 open() 系统调用生成,随 close() 系统调用销毁。它仅存在于进程的上下文中,代表进程对某个文件的 “打开实例”,记录了该进程访问文件的具体方式和状态。

核心功能

  • 跟踪进程对文件的当前偏移量(读写位置);
  • 记录文件的打开模式(如只读 O_RDONLY、读写 O_RDWR、追加 O_APPEND 等);
  • 关联文件的底层元数据(通过指向 dentry 和 inode,间接关联文件的物理信息);
  • 实现文件描述符(fd)与实际文件的映射(进程通过 fd 操作 file 对象)。

file 对象与其他文件系统对象的关系

file 对象、dentry(目录项)、inode(索引节点)是 VFS(虚拟文件系统)的三大核心对象,三者的关系如下:

对象类型 本质 核心关联 唯一性特征
file 进程打开的文件实例 通过 f_path.dentry 指向 dentry 同一文件可被多个进程打开,对应多个 file 对象
dentry 文件名与 inode 的映射 通过 d_inode 指向 inode 同一文件的硬链接对应多个 dentry,但指向同一 inode
inode 文件的物理元数据 记录文件权限、数据块指针等 唯一标识一个文件(跨进程)

形象比喻

  • inode 是文件的 “身份证”(唯一标识物理文件);
  • dentry 是文件的 “户口本”(记录文件名及家庭关系);
  • file 对象是进程的 “借阅证”(记录某进程如何使用该文件)。

file 对象的核心属性

file 对象包含多个关键成员,决定了进程与文件的交互方式,主要包括:

阅读全文 »